lightview 2.1.0 → 2.2.2

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 (71) hide show
  1. package/build-bundles.mjs +2 -6
  2. package/build.js +236 -46
  3. package/components/data-display/avatar.js +25 -1
  4. package/components/data-display/chart.js +22 -5
  5. package/components/data-display/countdown.js +3 -2
  6. package/components/data-input/checkbox.js +23 -1
  7. package/components/data-input/input.js +24 -1
  8. package/components/data-input/radio.js +37 -2
  9. package/components/data-input/select.js +24 -1
  10. package/components/data-input/toggle.js +21 -1
  11. package/components/navigation/breadcrumbs.js +42 -2
  12. package/docs/assets/js/examplify.js +1 -1
  13. package/docs/cdom-nav.html +32 -6
  14. package/docs/cdom.html +610 -180
  15. package/docs/components/avatar.html +24 -54
  16. package/docs/components/badge.html +14 -14
  17. package/docs/components/breadcrumbs.html +95 -29
  18. package/docs/components/chart-area.html +3 -3
  19. package/docs/components/chart-bar.html +4 -181
  20. package/docs/components/chart-column.html +4 -189
  21. package/docs/components/chart-line.html +3 -3
  22. package/docs/components/chart-pie.html +112 -166
  23. package/docs/components/chart.html +11 -13
  24. package/docs/components/checkbox.html +48 -28
  25. package/docs/components/collapse.html +6 -6
  26. package/docs/components/countdown.html +12 -12
  27. package/docs/components/dropdown.html +1 -1
  28. package/docs/components/file-input.html +4 -4
  29. package/docs/components/footer.html +11 -11
  30. package/docs/components/input.html +45 -29
  31. package/docs/components/join.html +4 -4
  32. package/docs/components/kbd.html +3 -3
  33. package/docs/components/loading.html +41 -53
  34. package/docs/components/pagination.html +4 -4
  35. package/docs/components/progress.html +6 -4
  36. package/docs/components/radio.html +42 -31
  37. package/docs/components/select.html +48 -59
  38. package/docs/components/toggle.html +44 -25
  39. package/docs/getting-started/index.html +4 -4
  40. package/jprx/LICENSE +21 -0
  41. package/jprx/README.md +130 -0
  42. package/{cdom → jprx}/helpers/array.js +9 -4
  43. package/{cdom → jprx}/helpers/state.js +6 -3
  44. package/jprx/index.js +69 -0
  45. package/jprx/package.json +24 -0
  46. package/jprx/parser.js +1517 -0
  47. package/lightview-all.js +3785 -1
  48. package/lightview-cdom.js +2128 -1
  49. package/lightview-router.js +179 -208
  50. package/lightview-x.js +1435 -1
  51. package/lightview.js +613 -1
  52. package/package.json +5 -2
  53. package/src/lightview-cdom.js +201 -49
  54. package/src/lightview-router.js +210 -0
  55. package/src/lightview-x.js +104 -55
  56. package/src/lightview.js +12 -1
  57. package/{watch.js → start-dev.js} +2 -1
  58. package/tests/cdom/parser.test.js +83 -12
  59. package/wrangler.toml +0 -3
  60. package/cdom/parser.js +0 -602
  61. package/test-text-tag.js +0 -6
  62. /package/{cdom → jprx}/helpers/compare.js +0 -0
  63. /package/{cdom → jprx}/helpers/conditional.js +0 -0
  64. /package/{cdom → jprx}/helpers/datetime.js +0 -0
  65. /package/{cdom → jprx}/helpers/format.js +0 -0
  66. /package/{cdom → jprx}/helpers/logic.js +0 -0
  67. /package/{cdom → jprx}/helpers/lookup.js +0 -0
  68. /package/{cdom → jprx}/helpers/math.js +0 -0
  69. /package/{cdom → jprx}/helpers/network.js +0 -0
  70. /package/{cdom → jprx}/helpers/stats.js +0 -0
  71. /package/{cdom → jprx}/helpers/string.js +0 -0
@@ -613,41 +613,52 @@ $('#example').content(reactiveDemo);</code></pre>
613
613
  </table>
614
614
  </div>
615
615
 
616
- <!-- Colors Example -->
617
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Colors</h2>
618
- <div style="display: flex; gap: 0.5rem; margin-bottom: 2rem;">
619
- <input type="radio" class="radio" checked name="color-demo" />
620
- <input type="radio" class="radio radio-primary" checked name="color-demo2" />
621
- <input type="radio" class="radio radio-secondary" checked name="color-demo3" />
622
- <input type="radio" class="radio radio-accent" checked name="color-demo4" />
623
- <input type="radio" class="radio radio-success" checked name="color-demo5" />
624
- <input type="radio" class="radio radio-warning" checked name="color-demo6" />
625
- <input type="radio" class="radio radio-info" checked name="color-demo7" />
626
- <input type="radio" class="radio radio-error" checked name="color-demo8" />
616
+ <!-- Custom Element Gallery -->
617
+ <h2 class="text-xl font-bold" style="margin-top: 2rem; margin-bottom: 1rem;">Radio Gallery</h2>
618
+ <p class="text-sm" style="opacity: 0.7; margin-bottom: 1.5rem;">
619
+ Live examples using <code>&lt;lv-radio&gt;</code> and <code>&lt;lv-radio-group&gt;</code> custom
620
+ elements.
621
+ </p>
622
+
623
+ <script type="module" src="/components/data-input/radio.js"></script>
624
+
625
+ <!-- Colors -->
626
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Colors</h3>
627
+ <div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
628
+ <lv-radio label="Default" checked name="color-gallery"></lv-radio>
629
+ <lv-radio label="Primary" color="primary" checked name="color-gallery-1"></lv-radio>
630
+ <lv-radio label="Secondary" color="secondary" checked name="color-gallery-2"></lv-radio>
631
+ <lv-radio label="Accent" color="accent" checked name="color-gallery-3"></lv-radio>
632
+ <lv-radio label="Success" color="success" checked name="color-gallery-4"></lv-radio>
633
+ <lv-radio label="Warning" color="warning" checked name="color-gallery-5"></lv-radio>
634
+ <lv-radio label="Info" color="info" checked name="color-gallery-6"></lv-radio>
635
+ <lv-radio label="Error" color="error" checked name="color-gallery-7"></lv-radio>
627
636
  </div>
628
637
 
629
- <!-- Sizes Example -->
630
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Sizes</h2>
631
- <div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem;">
632
- <input type="radio" class="radio radio-xs" checked name="size-demo" />
633
- <input type="radio" class="radio radio-sm" checked name="size-demo2" />
634
- <input type="radio" class="radio radio-md" checked name="size-demo3" />
635
- <input type="radio" class="radio radio-lg" checked name="size-demo4" />
638
+ <!-- Sizes -->
639
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Sizes</h3>
640
+ <div style="display: flex; align-items: center; flex-wrap: wrap; gap: 1.5rem; margin-bottom: 2rem;">
641
+ <lv-radio size="xs" label="Extra Small" checked name="size-gallery-1"></lv-radio>
642
+ <lv-radio size="sm" label="Small" checked name="size-gallery-2"></lv-radio>
643
+ <lv-radio size="md" label="Medium" checked name="size-gallery-3"></lv-radio>
644
+ <lv-radio size="lg" label="Large" checked name="size-gallery-4"></lv-radio>
636
645
  </div>
637
646
 
638
- <!-- With Label -->
639
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">With Label</h2>
640
- <div style="display: flex; flex-direction: column; width: 13rem; margin-bottom: 2rem;">
641
- <label
642
- style="display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 0.5rem 0;">
643
- <span style="font-size: 0.875rem;">Option 1</span>
644
- <input type="radio" name="label-demo" class="radio radio-primary" checked />
645
- </label>
646
- <label
647
- style="display: flex; align-items: center; justify-content: space-between; cursor: pointer; padding: 0.5rem 0;">
648
- <span style="font-size: 0.875rem;">Option 2</span>
649
- <input type="radio" name="label-demo" class="radio radio-primary" />
650
- </label>
647
+ <!-- Radio Groups -->
648
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Radio Groups</h3>
649
+ <div style="display: flex; flex-direction: column; gap: 2rem; margin-bottom: 2rem; max-width: 28rem;">
650
+ <lv-radio-group label="Vertical Group (Default)" name="vertical-gallery"
651
+ options='["Option 1", "Option 2", "Option 3"]' color="primary">
652
+ </lv-radio-group>
653
+
654
+ <lv-radio-group label="Horizontal Group" name="horizontal-gallery" horizontal="true"
655
+ options='["Small", "Medium", "Large"]' color="secondary">
656
+ </lv-radio-group>
657
+
658
+ <lv-radio-group label="With Descriptions" name="desc-gallery"
659
+ options='[{"value":"standard","label":"Standard","description":"Ships in 3-5 days"},{"value":"express","label":"Express","description":"Ships in 1-2 days"}]'
660
+ color="accent">
661
+ </lv-radio-group>
651
662
  </div>
652
663
  </div>
653
664
  </div>
@@ -625,70 +625,59 @@ $('#example').content(reactiveDemo);</code></pre>
625
625
  </table>
626
626
  </div>
627
627
 
628
- <!-- Sizes Example -->
629
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Sizes</h2>
628
+ <!-- Custom Element Gallery -->
629
+ <h2 class="text-xl font-bold" style="margin-top: 2rem; margin-bottom: 1rem;">Select Gallery</h2>
630
+ <p class="text-sm" style="opacity: 0.7; margin-bottom: 1.5rem;">
631
+ Live examples using <code>&lt;lv-select&gt;</code> custom elements.
632
+ </p>
633
+
634
+ <script type="module" src="/components/data-input/select.js"></script>
635
+
636
+ <!-- Sizes -->
637
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Sizes</h3>
638
+ <div
639
+ style="display: flex; flex-direction: column; gap: 1rem; max-width: 20rem; margin-bottom: 2rem;">
640
+ <lv-select size="xs" options='["Extra Small"]'></lv-select>
641
+ <lv-select size="sm" options='["Small"]'></lv-select>
642
+ <lv-select options='["Medium (default)"]'></lv-select>
643
+ <lv-select size="lg" options='["Large"]'></lv-select>
644
+ </div>
645
+
646
+ <!-- Colors -->
647
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Colors</h3>
630
648
  <div
631
- style="display: flex; flex-direction: column; gap: 0.5rem; max-width: 20rem; margin-bottom: 2rem;">
632
- <select class="select select-xs">
633
- <option>Extra Small</option>
634
- </select>
635
- <select class="select select-sm">
636
- <option>Small</option>
637
- </select>
638
- <select class="select">
639
- <option>Medium</option>
640
- </select>
641
- <select class="select select-lg">
642
- <option>Large</option>
643
- </select>
649
+ style="display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: 1rem; margin-bottom: 2rem;">
650
+ <lv-select color="primary" options='["Primary"]'></lv-select>
651
+ <lv-select color="secondary" options='["Secondary"]'></lv-select>
652
+ <lv-select color="accent" options='["Accent"]'></lv-select>
653
+ <lv-select color="info" options='["Info"]'></lv-select>
654
+ <lv-select color="success" options='["Success"]'></lv-select>
655
+ <lv-select color="warning" options='["Warning"]'></lv-select>
656
+ <lv-select color="error" options='["Error"]'></lv-select>
657
+ <lv-select ghost="true" options='["Ghost Style"]'></lv-select>
644
658
  </div>
645
659
 
646
- <!-- Colors Example -->
647
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Colors</h2>
648
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin-bottom: 2rem;">
649
- <select class="select select-primary">
650
- <option>Primary</option>
651
- </select>
652
- <select class="select select-secondary">
653
- <option>Secondary</option>
654
- </select>
655
- <select class="select select-accent">
656
- <option>Accent</option>
657
- </select>
658
- <select class="select select-info">
659
- <option>Info</option>
660
- </select>
661
- <select class="select select-success">
662
- <option>Success</option>
663
- </select>
664
- <select class="select select-warning">
665
- <option>Warning</option>
666
- </select>
667
- <select class="select select-error">
668
- <option>Error</option>
669
- </select>
670
- <select class="select select-ghost">
671
- <option>Ghost</option>
672
- </select>
660
+ <!-- Labels and Helpers -->
661
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Labels & Assistance</h3>
662
+ <div
663
+ style="display: flex; flex-direction: column; gap: 1.5rem; margin-bottom: 2rem; max-width: 28rem;">
664
+ <lv-select label="Country" placeholder="Select your country"
665
+ options='["United States", "United Kingdom", "Canada", "Other"]'
666
+ helper="Used for shipping calculations.">
667
+ </lv-select>
668
+ <lv-select label="Required Field" placeholder="Must choose one"
669
+ options='["Choice A", "Choice B"]' required="true">
670
+ </lv-select>
671
+ <lv-select label="Error State" options='["Option 1", "Option 2"]'
672
+ error="Selection is required.">
673
+ </lv-select>
673
674
  </div>
674
675
 
675
- <!-- With Fieldset -->
676
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">With Label (Fieldset Pattern)</h2>
677
- <div style="max-width: 28rem; margin-bottom: 2rem;">
678
- <fieldset
679
- style="display: flex; flex-direction: column; gap: 0.5rem; border: none; padding: 0.5rem 0;">
680
- <legend
681
- style="font-size: 0.875rem; font-weight: 700; color: oklch(var(--bc)/0.8); padding: 0 0.5rem;">
682
- Country</legend>
683
- <select class="select" style="width: 100%;">
684
- <option disabled selected>Select your country</option>
685
- <option>United States</option>
686
- <option>United Kingdom</option>
687
- <option>Canada</option>
688
- </select>
689
- <p style="font-size: 0.875rem; color: oklch(var(--bc)/0.6); padding: 0 0.5rem;">Used for
690
- shipping calculations.</p>
691
- </fieldset>
676
+ <!-- States -->
677
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">States</h3>
678
+ <div
679
+ style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem; max-width: 28rem;">
680
+ <lv-select label="Disabled Select" options='["Locked value"]' disabled="true"></lv-select>
692
681
  </div>
693
682
  </div>
694
683
  </div>
@@ -528,35 +528,54 @@ $('#example').content(demo);</code></pre>
528
528
  </table>
529
529
  </div>
530
530
 
531
- <!-- Colors Example -->
532
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Colors</h2>
533
- <div class="example-flex" style="margin-bottom: 2rem;">
534
- <input type="checkbox" class="toggle" checked />
535
- <input type="checkbox" class="toggle toggle-primary" checked />
536
- <input type="checkbox" class="toggle toggle-secondary" checked />
537
- <input type="checkbox" class="toggle toggle-accent" checked />
538
- <input type="checkbox" class="toggle toggle-success" checked />
539
- <input type="checkbox" class="toggle toggle-warning" checked />
540
- <input type="checkbox" class="toggle toggle-info" checked />
541
- <input type="checkbox" class="toggle toggle-error" checked />
531
+ <!-- Custom Element Gallery -->
532
+ <h2 class="text-xl font-bold" style="margin-top: 2rem; margin-bottom: 1rem;">Toggle Gallery</h2>
533
+ <p class="text-sm" style="opacity: 0.7; margin-bottom: 1.5rem;">
534
+ Live examples using <code>&lt;lv-toggle&gt;</code> custom elements.
535
+ </p>
536
+
537
+ <script type="module" src="/components/data-input/toggle.js"></script>
538
+
539
+ <!-- Colors -->
540
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Colors</h3>
541
+ <div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
542
+ <lv-toggle label="Default" checked="true"></lv-toggle>
543
+ <lv-toggle label="Primary" color="primary" checked="true"></lv-toggle>
544
+ <lv-toggle label="Secondary" color="secondary" checked="true"></lv-toggle>
545
+ <lv-toggle label="Accent" color="accent" checked="true"></lv-toggle>
546
+ <lv-toggle label="Success" color="success" checked="true"></lv-toggle>
547
+ <lv-toggle label="Warning" color="warning" checked="true"></lv-toggle>
548
+ <lv-toggle label="Info" color="info" checked="true"></lv-toggle>
549
+ <lv-toggle label="Error" color="error" checked="true"></lv-toggle>
550
+ </div>
551
+
552
+ <!-- Sizes -->
553
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Sizes</h3>
554
+ <div style="display: flex; align-items: center; flex-wrap: wrap; gap: 1.5rem; margin-bottom: 2rem;">
555
+ <lv-toggle size="xs" label="Extra Small" checked="true"></lv-toggle>
556
+ <lv-toggle size="sm" label="Small" checked="true"></lv-toggle>
557
+ <lv-toggle size="md" label="Medium" checked="true"></lv-toggle>
558
+ <lv-toggle size="lg" label="Large" checked="true"></lv-toggle>
542
559
  </div>
543
560
 
544
- <!-- Sizes Example -->
545
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Sizes</h2>
546
- <div class="example-flex" style="margin-bottom: 2rem;">
547
- <input type="checkbox" class="toggle toggle-xs" checked />
548
- <input type="checkbox" class="toggle toggle-sm" checked />
549
- <input type="checkbox" class="toggle toggle-md" checked />
550
- <input type="checkbox" class="toggle toggle-lg" checked />
561
+ <!-- Labels and Descriptions -->
562
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Labels & Descriptions</h3>
563
+ <div
564
+ style="display: flex; flex-direction: column; gap: 1.5rem; margin-bottom: 2rem; max-width: 28rem;">
565
+ <lv-toggle label="Notifications" description="Enable push notifications for new messages."
566
+ color="primary" checked="true">
567
+ </lv-toggle>
568
+ <lv-toggle label="Public Profile" label-position="right"
569
+ description="Allow others to see your active status." color="secondary">
570
+ </lv-toggle>
551
571
  </div>
552
572
 
553
- <!-- With Label -->
554
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">With Label</h2>
555
- <div class="form-control w-52" style="margin-bottom: 2rem;">
556
- <label class="label cursor-pointer">
557
- <span class="label-text">Remember me</span>
558
- <input type="checkbox" class="toggle toggle-primary" checked />
559
- </label>
573
+ <!-- States -->
574
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">States</h3>
575
+ <div
576
+ style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem; max-width: 28rem;">
577
+ <lv-toggle label="Disabled Off" disabled="true"></lv-toggle>
578
+ <lv-toggle label="Disabled On" checked="true" disabled="true"></lv-toggle>
560
579
  </div>
561
580
  </div>
562
581
  </div>
@@ -380,7 +380,7 @@ $('#app').content(App);`,
380
380
  {
381
381
  button: {
382
382
  class: 'format-btn html',
383
- href: './reviews.html',
383
+ href: '/docs/getting-started/reviews.html',
384
384
  target: '#reviews-box',
385
385
  onclick: () => loadFormat('HTML', './reviews.html'),
386
386
  children: ['📄 HTML']
@@ -389,7 +389,7 @@ $('#app').content(App);`,
389
389
  {
390
390
  button: {
391
391
  class: 'format-btn vdom',
392
- href: './reviews.vdom',
392
+ href: '/docs/getting-started/reviews.vdom',
393
393
  target: '#reviews-box',
394
394
  onclick: () => loadFormat('VDOM', './reviews.vdom'),
395
395
  children: ['🔷 VDOM']
@@ -398,7 +398,7 @@ $('#app').content(App);`,
398
398
  {
399
399
  button: {
400
400
  class: 'format-btn odom',
401
- href: './reviews.odom',
401
+ href: '/docs/getting-started/reviews.odom',
402
402
  target: '#reviews-box',
403
403
  onclick: () => loadFormat('ODOM', './reviews.odom'),
404
404
  children: ['🔶 Object DOM']
@@ -678,7 +678,7 @@ $('#app').content(App);`,
678
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>
679
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>
680
680
  </ul>
681
- <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>`
682
682
  }
683
683
  };
684
684
 
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.
@@ -17,9 +17,14 @@ export const map = (arr, transform) => {
17
17
  if (typeof transform === 'string') {
18
18
  return arr.map(item => (item && typeof item === 'object') ? item[transform] : item);
19
19
  }
20
- if (transform && transform.isLazy) {
20
+ // Check for LazyValue (has isLazy property and resolve method)
21
+ if (transform && transform.isLazy && typeof transform.resolve === 'function') {
21
22
  return arr.map(item => transform.resolve(item));
22
23
  }
24
+ // If it's a plain function
25
+ if (typeof transform === 'function') {
26
+ return arr.map(transform);
27
+ }
23
28
  return arr;
24
29
  };
25
30
 
@@ -54,9 +59,9 @@ export const length = (arg) => Array.isArray(arg) ? arg.length : (arg ? String(a
54
59
 
55
60
  export const registerArrayHelpers = (register) => {
56
61
  register('count', count);
57
- register('filter', filter);
58
- register('map', map);
59
- register('find', find);
62
+ register('filter', filter, { lazyAware: true });
63
+ register('map', map, { lazyAware: true });
64
+ register('find', find, { lazyAware: true });
60
65
  register('unique', unique);
61
66
  register('sort', sort);
62
67
  register('reverse', reverse);
@@ -14,19 +14,22 @@ export const set = (target, val) => {
14
14
  };
15
15
 
16
16
  export const increment = (target, by = 1) => {
17
- const current = (target && typeof target === 'object' && 'value' in target) ? target.value : 0;
17
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
18
+ const current = hasValue ? target.value : 0;
18
19
  const next = Number(current) + Number(by);
19
20
  return set(target, next);
20
21
  };
21
22
 
22
23
  export const decrement = (target, by = 1) => {
23
- const current = (target && typeof target === 'object' && 'value' in target) ? target.value : 0;
24
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
25
+ const current = hasValue ? target.value : 0;
24
26
  const next = Number(current) - Number(by);
25
27
  return set(target, next);
26
28
  };
27
29
 
28
30
  export const toggle = (target) => {
29
- const current = (target && typeof target === 'object' && 'value' in target) ? target.value : false;
31
+ const hasValue = target && (typeof target === 'object' || typeof target === 'function') && 'value' in target;
32
+ const current = hasValue ? target.value : false;
30
33
  return set(target, !current);
31
34
  };
32
35
 
package/jprx/index.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * JPRX - JSON Reactive Path eXpressions
3
+ * A reactive expression language for JSON data.
4
+ *
5
+ * JPRX extends JSON Pointer syntax with:
6
+ * - Reactive path resolution ($/path/to/value)
7
+ * - Helper functions (sum, map, filter, etc.)
8
+ * - Operators (++, --, +, -, *, /, etc.)
9
+ * - Explosion operator for array spreading (...prop)
10
+ * - Relative paths (../, ./)
11
+ */
12
+
13
+ // Core parser and expression resolution
14
+ export {
15
+ registerHelper,
16
+ registerOperator,
17
+ parseExpression,
18
+ resolvePath,
19
+ resolvePathAsContext,
20
+ resolveExpression,
21
+ parseCDOMC,
22
+ parseJPRX,
23
+ unwrapSignal,
24
+ getRegistry,
25
+ BindingTarget
26
+ } from './parser.js';
27
+
28
+ // Helper modules - these export registration functions
29
+ export { registerMathHelpers } from './helpers/math.js';
30
+ export { registerLogicHelpers } from './helpers/logic.js';
31
+ export { registerStringHelpers } from './helpers/string.js';
32
+ export { registerArrayHelpers } from './helpers/array.js';
33
+ export { registerCompareHelpers } from './helpers/compare.js';
34
+ export { registerConditionalHelpers } from './helpers/conditional.js';
35
+ export { registerDateTimeHelpers } from './helpers/datetime.js';
36
+ export { registerFormatHelpers } from './helpers/format.js';
37
+ export { registerLookupHelpers } from './helpers/lookup.js';
38
+ export { registerStatsHelpers } from './helpers/stats.js';
39
+ export { registerStateHelpers, set } from './helpers/state.js';
40
+ export { registerNetworkHelpers } from './helpers/network.js';
41
+
42
+ // Convenience function to register all standard helpers
43
+ export const registerAllHelpers = (registerFn) => {
44
+ const { registerMathHelpers } = require('./helpers/math.js');
45
+ const { registerLogicHelpers } = require('./helpers/logic.js');
46
+ const { registerStringHelpers } = require('./helpers/string.js');
47
+ const { registerArrayHelpers } = require('./helpers/array.js');
48
+ const { registerCompareHelpers } = require('./helpers/compare.js');
49
+ const { registerConditionalHelpers } = require('./helpers/conditional.js');
50
+ const { registerDateTimeHelpers } = require('./helpers/datetime.js');
51
+ const { registerFormatHelpers } = require('./helpers/format.js');
52
+ const { registerLookupHelpers } = require('./helpers/lookup.js');
53
+ const { registerStatsHelpers } = require('./helpers/stats.js');
54
+ const { registerStateHelpers } = require('./helpers/state.js');
55
+ const { registerNetworkHelpers } = require('./helpers/network.js');
56
+
57
+ registerMathHelpers(registerFn);
58
+ registerLogicHelpers(registerFn);
59
+ registerStringHelpers(registerFn);
60
+ registerArrayHelpers(registerFn);
61
+ registerCompareHelpers(registerFn);
62
+ registerConditionalHelpers(registerFn);
63
+ registerDateTimeHelpers(registerFn);
64
+ registerFormatHelpers(registerFn);
65
+ registerLookupHelpers(registerFn);
66
+ registerStatsHelpers(registerFn);
67
+ registerStateHelpers((name, fn) => registerFn(name, fn, { pathAware: true }));
68
+ registerNetworkHelpers(registerFn);
69
+ };