@wrnrlr/prelude 0.0.1 → 0.1.3
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/.github/workflows/publish.yml +46 -5
- package/deno.json +18 -9
- package/package.json +4 -6
- package/readme.md +163 -41
- package/src/constants.ts +2 -1
- package/src/{controlflow.js → controlflow.ts} +63 -50
- package/src/hyperscript.ts +7 -6
- package/src/mod.ts +19 -17
- package/src/reactive.ts +42 -14
- package/src/resource.js +184 -0
- package/src/router.js +65 -0
- package/src/runtime.ts +9 -8
- package/test/hyperscript.js +2 -2
- package/test/reactive.js +12 -4
- package/www/assets/css/presets.css +504 -0
- package/www/assets/css/style.css +90 -0
- package/www/demo.html +15 -0
- package/www/index.html +211 -0
- package/www/playground.html +183 -0
- package/www/public/example/admin.html +88 -0
- package/{example → www/public/example}/counter.html +1 -1
- package/{example → www/public/example}/greeting.html +1 -1
- package/{example → www/public/example}/show.html +1 -1
- package/{example → www/public/example}/todo.html +1 -1
- package/www/public/fonts/fab.ttf +0 -0
- package/www/public/fonts/fab.woff2 +0 -0
- package/www/public/fonts/far.ttf +0 -0
- package/www/public/fonts/far.woff2 +0 -0
- package/www/public/fonts/fas.ttf +0 -0
- package/www/public/fonts/fas.woff2 +0 -0
- package/www/public/logo.svg +16 -0
- package/www/typedoc.json +13 -0
- package/www/vite.config.js +106 -0
- package/example/paint.html +0 -22
- package/index.html +0 -230
- package/presets.css +0 -284
- package/public/logo.svg +0 -5
- package/test/runtime.js +0 -7
- package/typedoc.jsonc +0 -31
- /package/{public → www/public}/banner.svg +0 -0
| @@ -5,12 +5,53 @@ on: | |
| 5 5 | 
             
                branches:
         | 
| 6 6 | 
             
                  - main
         | 
| 7 7 |  | 
| 8 | 
            +
            permissions:
         | 
| 9 | 
            +
              contents: read
         | 
| 10 | 
            +
              pages: write
         | 
| 11 | 
            +
              id-token: write
         | 
| 12 | 
            +
             | 
| 8 13 | 
             
            jobs:
         | 
| 9 14 | 
             
              publish:
         | 
| 10 15 | 
             
                runs-on: ubuntu-latest
         | 
| 11 | 
            -
                 | 
| 12 | 
            -
                   | 
| 13 | 
            -
                   | 
| 16 | 
            +
                environment:
         | 
| 17 | 
            +
                  name: github-pages
         | 
| 18 | 
            +
                  url: ${{ steps.deployment.outputs.page_url }}
         | 
| 14 19 | 
             
                steps:
         | 
| 15 | 
            -
                  -  | 
| 16 | 
            -
             | 
| 20 | 
            +
                  - name: Check out the repository to the runner
         | 
| 21 | 
            +
                    uses: actions/checkout@v4
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  - name: Setup Deno
         | 
| 24 | 
            +
                    uses: denoland/setup-deno@v2
         | 
| 25 | 
            +
                    with:
         | 
| 26 | 
            +
                      deno-version: v2.x
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  - name: Deno install
         | 
| 29 | 
            +
                    run: deno install
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  # - name: Lint
         | 
| 32 | 
            +
                  #   run: deno lint
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  - name: Run tests
         | 
| 35 | 
            +
                    run: deno task test
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  - name: Build
         | 
| 38 | 
            +
                    run: deno task build
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  - name: Setup Pages
         | 
| 41 | 
            +
                    uses: actions/configure-pages@v5
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  - name: Upload artifact
         | 
| 44 | 
            +
                    uses: actions/upload-pages-artifact@v3
         | 
| 45 | 
            +
                    with:
         | 
| 46 | 
            +
                      path: './www/dist'
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  - name: Deploy to GitHub Pages
         | 
| 49 | 
            +
                    id: deployment
         | 
| 50 | 
            +
                    uses: actions/deploy-pages@v4
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # - name: Setup NPM
         | 
| 53 | 
            +
                  #   run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > /home/runner/.npmrc
         | 
| 54 | 
            +
                  #   env:
         | 
| 55 | 
            +
                  #     NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
         | 
| 56 | 
            +
                  # - name: Run release script
         | 
| 57 | 
            +
                  #   run: /home/runner/.deno/bin/deno task release
         | 
    
        package/deno.json
    CHANGED
    
    | @@ -1,23 +1,32 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@wrnrlr/prelude",
         | 
| 3 | 
            -
              "version": "0.1. | 
| 3 | 
            +
              "version": "0.1.3",
         | 
| 4 4 | 
             
              "exports": "./src/mod.ts",
         | 
| 5 5 | 
             
              "compilerOptions": {
         | 
| 6 | 
            -
                "strict":  | 
| 6 | 
            +
                "strict": false,
         | 
| 7 7 | 
             
                "checkJs": false,
         | 
| 8 8 | 
             
                "noImplicitThis": false,
         | 
| 9 | 
            -
                "lib": ["dom","dom.iterable","dom.asynciterable","deno.ns"]
         | 
| 9 | 
            +
                "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
         | 
| 10 10 | 
             
              },
         | 
| 11 11 | 
             
              "imports": {
         | 
| 12 12 | 
             
                "@std/assert": "jsr:@std/assert@^1.0.0",
         | 
| 13 | 
            +
                "@std/fs": "jsr:@std/fs@^1.0.5",
         | 
| 14 | 
            +
                "@std/path": "jsr:@std/path@^1.0.6",
         | 
| 13 15 | 
             
                "@std/testing": "jsr:@std/testing@^0.225.3",
         | 
| 14 | 
            -
                " | 
| 15 | 
            -
                " | 
| 16 | 
            -
                " | 
| 16 | 
            +
                "jsdom": "npm:jsdom",
         | 
| 17 | 
            +
                "typedoc": "npm:typedoc@^0.26.6",
         | 
| 18 | 
            +
                "vite": "npm:vite@^5.4.9",
         | 
| 19 | 
            +
                "esbuild": "npm:esbuild@^0.24.0"
         | 
| 17 20 | 
             
              },
         | 
| 18 21 | 
             
              "tasks": {
         | 
| 19 | 
            -
                "dev": "deno run -A npm:vite | 
| 20 | 
            -
                "test": "deno test -A  | 
| 21 | 
            -
                "docs": "deno run -A npm:typedoc "
         | 
| 22 | 
            +
                "dev": "deno run -A npm:vite --config www/vite.config.js",
         | 
| 23 | 
            +
                "test": "deno test -A ./test/*.[jt]s",
         | 
| 24 | 
            +
                "docs": "deno run -A npm:typedoc --options www/typedoc.json",
         | 
| 25 | 
            +
                "build": "deno run -A npm:vite build --mode production --config www/vite.config.js",
         | 
| 26 | 
            +
                "release": "deno publish --allow-slow-types --allow-dirty && npm publish --access public",
         | 
| 27 | 
            +
                "clean": "rm -rf dist/ www/dist www/docs"
         | 
| 28 | 
            +
              },
         | 
| 29 | 
            +
              "lint": {
         | 
| 30 | 
            +
                "include": ["src"]
         | 
| 22 31 | 
             
              }
         | 
| 23 32 | 
             
            }
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,12 +1,10 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@wrnrlr/prelude",
         | 
| 3 | 
            -
              " | 
| 3 | 
            +
              "type": "module",
         | 
| 4 | 
            +
              "version": "0.1.3",
         | 
| 4 5 | 
             
              "author": "Werner Laurensse",
         | 
| 5 6 | 
             
              "description": "A signal based frontend library with fine-grained reactivity",
         | 
| 6 7 | 
             
              "main": "./src/mod.ts",
         | 
| 7 | 
            -
              " | 
| 8 | 
            -
             | 
| 9 | 
            -
                "test": "test"
         | 
| 10 | 
            -
              },
         | 
| 11 | 
            -
              "scripts": {}
         | 
| 8 | 
            +
              "scripts": {},
         | 
| 9 | 
            +
              "dependencies": { "@codemirror/lang-html": "^6.4.9", "codemirror": "^6.0.1" }
         | 
| 12 10 | 
             
            }
         | 
    
        package/readme.md
    CHANGED
    
    | @@ -1,86 +1,208 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # Prelude
         | 
| 2 | 
            +
            [Home](https://wrnrlr.github.io/prelude/) [NPM](https://www.npmjs.com/package/@wrnrlr/prelude) [JSR](https://jsr.io/@wrnrlr/prelude)
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Prelude lets you develop web applications in a familiar component-based functional style.
         | 
| 5 | 
            +
            It is build with the desire to have a lightweight frontend framework that works
         | 
| 6 | 
            +
            using just JavaScript but that nontheless can handle complex web applications without
         | 
| 7 | 
            +
            sacrificing on developer expierence.
         | 
| 2 8 |  | 
| 3 9 | 
             
            ## Get Started
         | 
| 4 10 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 11 | 
            +
            Prelude works with most popular JavaScript runtimes: Node, Deno, Bun or the borwser.
         | 
| 12 | 
            +
            It is available on NPM and JSR under the package named `@wrnrlr/prelude`.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            The quickest way to get started with Prelude is uing the [Playground](https://wrnrlr.github.io/prelude/playground) app on the homepage.
         | 
| 15 | 
            +
            It offers a IDE complete with a code editor, live preview and a number of examples.
         | 
| 16 | 
            +
            Aternativaly you can develop on your local machine using `vite`.
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            Some Prelude APIs can also be used in the REPL to expore their behaviour interactively.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## Basic Example
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            This is a example of a button that increments a counter when it is clicked.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ```html
         | 
| 25 | 
            +
            <!DOCTYPE html>
         | 
| 26 | 
            +
            <title>Counter</title>
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            <script type="module">
         | 
| 7 29 | 
             
              import {h, signal, render} from 'https://esm.sh/@wrnrlr/prelude'
         | 
| 8 | 
            -
              ```
         | 
| 9 30 |  | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
               | 
| 31 | 
            +
              function Counter() {
         | 
| 32 | 
            +
                const n = signal(1)
         | 
| 33 | 
            +
                return h('button', {onClick: e => n(n=>n+1)}, n)
         | 
| 34 | 
            +
              }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              render(Counter, document.body)
         | 
| 37 | 
            +
            </script>
         | 
| 38 | 
            +
            ```
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ## Hyperscript
         | 
| 14 41 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
               ```js
         | 
| 17 | 
            -
              import {h, signal, render} from 'jsr:wrnrlr/prelude'
         | 
| 18 | 
            -
              ```
         | 
| 42 | 
            +
            Prelude does not use JSX or a templating language to descript html instead we use a DSL called HyperScript.
         | 
| 19 43 |  | 
| 20 | 
            -
             | 
| 44 | 
            +
            The `h` function is used in either of two ways based on the type of the first argument,
         | 
| 45 | 
            +
            when it is a string it will create a html element like ,
         | 
| 46 | 
            +
            and when it is a function it will create a reactive component.
         | 
| 21 47 |  | 
| 22 48 | 
             
            ```js
         | 
| 23 | 
            -
             | 
| 49 | 
            +
            h('div',{},[
         | 
| 50 | 
            +
              h('label','Name'),h('input',{})
         | 
| 51 | 
            +
            ])
         | 
| 52 | 
            +
            ```
         | 
| 24 53 |  | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 54 | 
            +
            ### Event Handler
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            Prelude tries to integrate with the existing web APIs as much as possible, handeling user events is no different,
         | 
| 57 | 
            +
            use a function to the event callback.
         | 
| 29 58 |  | 
| 30 | 
            -
             | 
| 59 | 
            +
            In the following example we listen for `onclick` events for a button, and increment the value of the `n` signal.
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            ```js
         | 
| 62 | 
            +
            h('button', {onClick:e => n(i => i + 1)}, n)
         | 
| 31 63 | 
             
            ```
         | 
| 32 64 |  | 
| 33 | 
            -
             | 
| 65 | 
            +
            Be adviced, the event handler MUST always have one argument even if this is not being used, lest HyperScript confuses it for a signal
         | 
| 66 | 
            +
            and ignores the events.
         | 
| 34 67 |  | 
| 35 68 | 
             
            ```js
         | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 69 | 
            +
            // Ok
         | 
| 70 | 
            +
            h('button', {onClick: e => console.log('Ok')})
         | 
| 71 | 
            +
            h('button', {onClick: _ => console.log('Ok')})
         | 
| 72 | 
            +
            // This event handler will be ignored
         | 
| 73 | 
            +
            h('button', {onClick: () => console.log('Wrong')})
         | 
| 74 | 
            +
            ```
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            ## Reactivity
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            ### Signals
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            A signal is an object that holds a value with a setter to update this value and a getter that returns this value whenever it is updated.
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            Create signal.
         | 
| 83 | 
            +
            ```js
         | 
| 84 | 
            +
            // Create a signal with value one
         | 
| 85 | 
            +
            const n = signal(1)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            // Get value from signal
         | 
| 88 | 
            +
            n()
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            // Set value for signal
         | 
| 91 | 
            +
            n(2)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            // Set value with an update function
         | 
| 94 | 
            +
            n(i=>i+1)
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            // Derived signal
         | 
| 97 | 
            +
            const n2 = () => n() * 2
         | 
| 98 | 
            +
            ```
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            ### Effects
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            The `effect` function lets you subscribe to signals and perform side-effects whenever the signal chages.
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            ```js
         | 
| 105 | 
            +
            const a = signal(1), b = signal(2)
         | 
| 38 106 | 
             
            effect(() => console.log('a+b', a()+b()))
         | 
| 39 107 | 
             
            const c = () => a()+b()
         | 
| 40 108 | 
             
            effect(() => console.log('c', c()))
         | 
| 41 109 | 
             
            a(i => i+1)
         | 
| 42 110 | 
             
            ```
         | 
| 43 111 |  | 
| 44 | 
            -
             | 
| 112 | 
            +
            ### Memo
         | 
| 45 113 |  | 
| 46 | 
            -
            The ` | 
| 114 | 
            +
            The `memo` function caches the result of the function passed to it.
         | 
| 47 115 |  | 
| 48 116 | 
             
            ```js
         | 
| 49 | 
            -
             | 
| 50 | 
            -
              h('label','Name'),h('input',{})
         | 
| 51 | 
            -
            ])
         | 
| 117 | 
            +
            const n2 = memo(() => n() * 2)
         | 
| 52 118 | 
             
            ```
         | 
| 53 119 |  | 
| 54 | 
            -
             | 
| 120 | 
            +
            ### Untrack
         | 
| 55 121 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
            otherwise hyperscript will confuse it for a signal.
         | 
| 122 | 
            +
            ## Conditional Rendering
         | 
| 58 123 |  | 
| 59 124 | 
             
            ```js
         | 
| 60 | 
            -
             | 
| 61 | 
            -
            h('button', {onClick: e => console.log('Ok')}, 'Hi')
         | 
| 62 | 
            -
            h('button', {onClick: _ => console.log('Ok')}, 'Hi')
         | 
| 63 | 
            -
            // This event handler will be ignored
         | 
| 64 | 
            -
            h('button', {onClick: () => console.log('Wrong')}, '')
         | 
| 125 | 
            +
            h(Show, {when:() => n()%2 === 0, fallback:'odd'}, 'even')
         | 
| 65 126 | 
             
            ```
         | 
| 66 127 |  | 
| 67 | 
            -
             | 
| 128 | 
            +
            It is also possible to conditionally render a component by prefixing it with a JavaScript *and-expression*, like in the example below,
         | 
| 129 | 
            +
            but using `Show` is going to be faster.
         | 
| 68 130 |  | 
| 69 131 | 
             
            ```js
         | 
| 70 | 
            -
            h( | 
| 132 | 
            +
            h('',show&&'Hi')
         | 
| 71 133 | 
             
            ```
         | 
| 72 134 |  | 
| 73 135 | 
             
            ## Rendering Lists
         | 
| 74 136 |  | 
| 75 137 | 
             
            ```js
         | 
| 76 | 
            -
            h(List, {each: | 
| 138 | 
            +
            h(List, {each:['a','b','c']}, (v,i)=>`${i()}:${v()}`)
         | 
| 139 | 
            +
            ```
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            ## Fetching Resources
         | 
| 142 | 
            +
             | 
| 143 | 
            +
            The `resource()` function lets you define a asynchronous signal.
         | 
| 144 | 
            +
             | 
| 145 | 
            +
             | 
| 146 | 
            +
            ```js
         | 
| 147 | 
            +
            resource(async ()=>getPosts())
         | 
| 77 148 | 
             
            ```
         | 
| 78 149 |  | 
| 79 | 
            -
            ##  | 
| 150 | 
            +
            ## Dependency Injection
         | 
| 80 151 | 
             
            Prelude supports dependency injection with the `contect` and `useContext` APIs.
         | 
| 81 152 |  | 
| 82 | 
            -
             | 
| 153 | 
            +
            ```js
         | 
| 154 | 
            +
            const CounterCtx = context()
         | 
| 155 | 
            +
            const useCounter = () => useContext(CounterCtx)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            function CounterProvider(props) {
         | 
| 158 | 
            +
              const count = signal(0)
         | 
| 159 | 
            +
              const increment = () => count(i=>i+1)
         | 
| 160 | 
            +
              return h(CounterCtx.Provider, {value:[count,increment]}, props.children)
         | 
| 161 | 
            +
            }
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            function Counter() {
         | 
| 164 | 
            +
              const [n, increment] = useCounter()
         | 
| 165 | 
            +
              return h('button', {onClick:e=>increment()}, n)
         | 
| 166 | 
            +
            }
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            function
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            function App() {
         | 
| 171 | 
            +
              h(CounterProvider, h(Counter))
         | 
| 172 | 
            +
            }
         | 
| 173 | 
            +
            ```
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            ## Router
         | 
| 83 176 |  | 
| 84 177 | 
             
            ```js
         | 
| 85 | 
            -
            h( | 
| 178 | 
            +
            h(Router,[
         | 
| 179 | 
            +
              {path:'/', component:Posts},
         | 
| 180 | 
            +
              {path:'/user', component:Users}
         | 
| 181 | 
            +
            ])
         | 
| 86 182 | 
             
            ```
         | 
| 183 | 
            +
             | 
| 184 | 
            +
            ## Learn More
         | 
| 185 | 
            +
             | 
| 186 | 
            +
            * [API Reference]()
         | 
| 187 | 
            +
            * [SolidJS Docs]():
         | 
| 188 | 
            +
              The documentation of SolidJS also a good place for background information because Prelude is lacking extensive documentation at this time.
         | 
| 189 | 
            +
              Prelude started as a SolidJS clone, but with better HyperScript support. A lot of the concepts are the same but naming conventions can vary.
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            ## TODO
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            * [ ] tailwind styling
         | 
| 194 | 
            +
            * SSR
         | 
| 195 | 
            +
            * Hydration
         | 
| 196 | 
            +
            * Components
         | 
| 197 | 
            +
              * [ ] Select
         | 
| 198 | 
            +
              * [ ] Multiselect
         | 
| 199 | 
            +
              * [ ] DataTable
         | 
| 200 | 
            +
              * [ ] Dropdown
         | 
| 201 | 
            +
              * [ ] Dialog
         | 
| 202 | 
            +
              * [ ] Dynamic
         | 
| 203 | 
            +
             | 
| 204 | 
            +
            ## Links
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            * [Homepage](https://wrnrlr.github.io/prelude)
         | 
| 207 | 
            +
            * [NPM](https://www.npmjs.com/package/@wrnrlr/prelude)
         | 
| 208 | 
            +
            * [JSR](https://jsr.io/@wrnrlr/prelude)
         | 
    
        package/src/constants.ts
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            // @ts-nocheck:
         | 
| 1 2 | 
             
            /**
         | 
| 2 3 | 
             
            * Non-breakable space in Unicode
         | 
| 3 4 | 
             
            * @group Utils
         | 
| @@ -13,7 +14,7 @@ declare type DocumentFragment = any | |
| 13 14 | 
             
            export declare type Node = any
         | 
| 14 15 |  | 
| 15 16 | 
             
            export type Mountable = Elem | Document | ShadowRoot | DocumentFragment | Node;
         | 
| 16 | 
            -
            type ExpandableNode = Node & { [key: string]:  | 
| 17 | 
            +
            type ExpandableNode = Node & { [key: string]: unknown };
         | 
| 17 18 |  | 
| 18 19 | 
             
            // type Expect<T extends true> = T;
         | 
| 19 20 | 
             
            // type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
         | 
| @@ -1,81 +1,94 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            // @ts-nocheck:
         | 
| 2 | 
            +
            import type { Child } from './hyperscript.ts'
         | 
| 3 | 
            +
            import {signal,untrack,batch,memo,root,type Signal} from './reactive.ts'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            export type ShowProps<T> = {
         | 
| 6 | 
            +
              when: T,
         | 
| 7 | 
            +
              children: Child | ((a:()=>T)=>void),
         | 
| 8 | 
            +
              fallback: unknown
         | 
| 9 | 
            +
            }
         | 
| 2 10 |  | 
| 3 11 | 
             
            /**
         | 
| 4 12 | 
             
            Show children if `when` prop is true, otherwise show `fallback`.
         | 
| 5 13 | 
             
            @group Components
         | 
| 6 14 | 
             
            */
         | 
| 7 | 
            -
            export function Show(props) {
         | 
| 15 | 
            +
            export function Show<T>(props:ShowProps<T>) {
         | 
| 8 16 | 
             
              const condition = memo(()=>props.when)
         | 
| 9 17 | 
             
              return memo(()=>{
         | 
| 10 18 | 
             
                const c = condition()
         | 
| 11 19 | 
             
                if (c) {
         | 
| 12 20 | 
             
                  const child = props.children
         | 
| 13 21 | 
             
                  const fn = typeof child === "function" && child.length > 0
         | 
| 14 | 
            -
                  return fn ?  | 
| 22 | 
            +
                  return fn ? untrack(() => child(() => props.when)) : child
         | 
| 15 23 | 
             
                } else return props.fallback
         | 
| 16 24 | 
             
              })
         | 
| 17 25 | 
             
            }
         | 
| 18 26 |  | 
| 19 | 
            -
             | 
| 20 | 
            -
               | 
| 21 | 
            -
               | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
               | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
              }
         | 
| 33 | 
            -
              throw new Error('Cannot wrap signal')
         | 
| 27 | 
            +
            type ItemHolder = {
         | 
| 28 | 
            +
              index:number,
         | 
| 29 | 
            +
              indexSetter?:any,
         | 
| 30 | 
            +
              value:unknown,
         | 
| 31 | 
            +
              valueSetter:any,
         | 
| 32 | 
            +
              disposer: any
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            export type ListProps<T> = {
         | 
| 36 | 
            +
              when: T,
         | 
| 37 | 
            +
              each: any,
         | 
| 38 | 
            +
              children: Child | ((a:()=>T)=>void),
         | 
| 39 | 
            +
              fallback: unknown
         | 
| 34 40 | 
             
            }
         | 
| 35 41 |  | 
| 36 42 | 
             
            /**
         | 
| 37 43 | 
             
            List
         | 
| 38 44 | 
             
            @group Components
         | 
| 39 45 | 
             
            */
         | 
| 40 | 
            -
            export function List(props) {
         | 
| 46 | 
            +
            export function List<T>(props:ListProps<T>) {
         | 
| 41 47 | 
             
              const fallback = "fallback" in props && { fallback: () => props.fallback }
         | 
| 42 48 | 
             
              const list = props.each
         | 
| 43 | 
            -
              const cb = props.children | 
| 44 | 
            -
              let items = [], | 
| 45 | 
            -
                 | 
| 46 | 
            -
             | 
| 49 | 
            +
              const cb:any = (props.children as any)?.call ? props.children : (v:any)=>v
         | 
| 50 | 
            +
              let items:ItemHolder[] = [],
         | 
| 51 | 
            +
                item: undefined|ItemHolder,
         | 
| 52 | 
            +
                // unusedItems,
         | 
| 53 | 
            +
                i: undefined|number,
         | 
| 54 | 
            +
                newValue: undefined|number,
         | 
| 55 | 
            +
                mapped: number[],
         | 
| 56 | 
            +
                oldIndex: number,
         | 
| 57 | 
            +
                oldValue: unknown
         | 
| 58 | 
            +
              const indexes = cb.length > 1 ? [] : null;
         | 
| 59 | 
            +
              function newValueGetter(_:unknown) { return newValue }
         | 
| 47 60 | 
             
              function changeBoth() {
         | 
| 48 | 
            -
                item | 
| 49 | 
            -
                item | 
| 50 | 
            -
                item | 
| 51 | 
            -
                item | 
| 61 | 
            +
                item!.index = i!
         | 
| 62 | 
            +
                item!.indexSetter?.(i)
         | 
| 63 | 
            +
                item!.value = newValue!
         | 
| 64 | 
            +
                item!.valueSetter?.(newValueGetter)
         | 
| 52 65 | 
             
              }
         | 
| 53 | 
            -
              function mapperWithIndexes(disposer) {
         | 
| 66 | 
            +
              function mapperWithIndexes(disposer:any) {
         | 
| 54 67 | 
             
                const V = newValue, I = i, Is = signal(I), Vs = signal(V)
         | 
| 55 | 
            -
                items.push({value: newValue, index: I | 
| 68 | 
            +
                items.push({value: newValue, index: I!, disposer, indexSetter: Is, valueSetter: Vs})
         | 
| 56 69 | 
             
                return cb(
         | 
| 57 | 
            -
                  (...a) => a.length ?
         | 
| 58 | 
            -
                     | 
| 70 | 
            +
                  (...a:any[]) => a.length ?
         | 
| 71 | 
            +
                    untrack(()=>list((list:any)=>list.toSpliced(I,1,a[0])))
         | 
| 59 72 | 
             
                    : Vs(),
         | 
| 60 73 | 
             
                  ()=>Is())
         | 
| 61 74 | 
             
              }
         | 
| 62 | 
            -
              function mapperWithoutIndexes(disposer) {
         | 
| 75 | 
            +
              function mapperWithoutIndexes(disposer:any) {
         | 
| 63 76 | 
             
                const V = newValue, I = i, Vs = signal(V)
         | 
| 64 | 
            -
                items.push({value: V, index: i | 
| 65 | 
            -
                return cb((...a) => a.length ?
         | 
| 66 | 
            -
                   | 
| 77 | 
            +
                items.push({value: V, index: i!, disposer, valueSetter: Vs})
         | 
| 78 | 
            +
                return cb((...a:unknown[]) => a.length ?
         | 
| 79 | 
            +
                  untrack(()=>list((list:any)=>list.toSpliced(I,1,a[0])))
         | 
| 67 80 | 
             
                  : Vs())
         | 
| 68 81 | 
             
              }
         | 
| 69 82 | 
             
              const mapper = indexes ? mapperWithIndexes : mapperWithoutIndexes
         | 
| 70 83 | 
             
              return memo(() => {
         | 
| 71 | 
            -
                const newItems = list()  | 
| 84 | 
            +
                const newItems = list.call ? list() : list
         | 
| 72 85 | 
             
                // (newItems)[$TRACK]; // top level tracking
         | 
| 73 | 
            -
                return  | 
| 86 | 
            +
                return untrack(() => {
         | 
| 74 87 | 
             
                  const temp = new Array(newItems.length) // new mapped array
         | 
| 75 | 
            -
                  unusedItems = items.length
         | 
| 88 | 
            +
                  let unusedItems = items.length
         | 
| 76 89 |  | 
| 77 90 | 
             
                  // 1) no change when values & indexes match
         | 
| 78 | 
            -
                  for (j = unusedItems - 1; j >= 0; --j) {
         | 
| 91 | 
            +
                  for (let j = unusedItems - 1; j >= 0; --j) {
         | 
| 79 92 | 
             
                    item = items[j]
         | 
| 80 93 | 
             
                    oldIndex = item.index
         | 
| 81 94 | 
             
                    if (oldIndex < newItems.length && newItems[oldIndex] === item.value) {
         | 
| @@ -90,7 +103,7 @@ export function List(props) { | |
| 90 103 | 
             
                  // #2 prepare values matcher
         | 
| 91 104 | 
             
                  const matcher = new Map()
         | 
| 92 105 | 
             
                  const matchedItems = new Uint8Array(unusedItems)
         | 
| 93 | 
            -
                  for (j = unusedItems - 1; j >= 0; --j) {
         | 
| 106 | 
            +
                  for (let j = unusedItems - 1; j >= 0; --j) {
         | 
| 94 107 | 
             
                    oldValue = items[j].value
         | 
| 95 108 | 
             
                    matcher.get(oldValue)?.push(j) ?? matcher.set(oldValue, [j])
         | 
| 96 109 | 
             
                  }
         | 
| @@ -99,19 +112,19 @@ export function List(props) { | |
| 99 112 | 
             
                  for (i = 0; i < newItems.length; ++i) {
         | 
| 100 113 | 
             
                    if (i in temp) continue
         | 
| 101 114 | 
             
                    newValue = newItems[i]
         | 
| 102 | 
            -
                    j = matcher.get(newValue)?.pop() ?? -1
         | 
| 115 | 
            +
                    const j = matcher.get(newValue)?.pop() ?? -1
         | 
| 103 116 | 
             
                    if (j >= 0) {
         | 
| 104 | 
            -
                      item = items[j]
         | 
| 105 | 
            -
                      oldIndex = item | 
| 117 | 
            +
                      item = items[j as number]
         | 
| 118 | 
            +
                      oldIndex = item!.index
         | 
| 106 119 | 
             
                      temp[i] = mapped[oldIndex]
         | 
| 107 | 
            -
                      item | 
| 108 | 
            -
                      item | 
| 109 | 
            -
                      matchedItems[j] = 1
         | 
| 120 | 
            +
                      item!.index = i
         | 
| 121 | 
            +
                      item!.indexSetter?.(i)
         | 
| 122 | 
            +
                      matchedItems[j as number] = 1
         | 
| 110 123 | 
             
                    }
         | 
| 111 124 | 
             
                  }
         | 
| 112 125 |  | 
| 113 126 | 
             
                  // 3) reduce unusedItems for matched items
         | 
| 114 | 
            -
                  for (j = matchedItems.length - 1; j >= 0; --j) {
         | 
| 127 | 
            +
                  for (let j = matchedItems.length - 1; j >= 0; --j) {
         | 
| 115 128 | 
             
                    if (matchedItems[j] && --unusedItems !== j) {
         | 
| 116 129 | 
             
                      item = items[j]
         | 
| 117 130 | 
             
                      items[j] = items[unusedItems]
         | 
| @@ -120,9 +133,9 @@ export function List(props) { | |
| 120 133 | 
             
                  }
         | 
| 121 134 |  | 
| 122 135 | 
             
                  // 4) change values when indexes match
         | 
| 123 | 
            -
                  for (j = unusedItems - 1; j >= 0; --j) {
         | 
| 136 | 
            +
                  for (let j = unusedItems - 1; j >= 0; --j) {
         | 
| 124 137 | 
             
                    item = items[j];
         | 
| 125 | 
            -
                    oldIndex = item | 
| 138 | 
            +
                    oldIndex = item!.index;
         | 
| 126 139 | 
             
                    if (!(oldIndex in temp) && oldIndex < newItems.length) {
         | 
| 127 140 | 
             
                      temp[oldIndex] = mapped[oldIndex]
         | 
| 128 141 | 
             
                      newValue = newItems[oldIndex]
         | 
| @@ -156,7 +169,7 @@ export function List(props) { | |
| 156 169 | 
             
              })
         | 
| 157 170 | 
             
            }
         | 
| 158 171 |  | 
| 159 | 
            -
            function disposeList(list) {
         | 
| 172 | 
            +
            function disposeList(list:any[]) {
         | 
| 160 173 | 
             
              for (let i = 0; i < list.length; i++) {
         | 
| 161 174 | 
             
                list[i]?.disposer()
         | 
| 162 175 | 
             
              }
         | 
    
        package/src/hyperscript.ts
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
            import { | 
| 1 | 
            +
            // @ts-nocheck:
         | 
| 2 | 
            +
            import {untrack} from './reactive.ts'
         | 
| 3 | 
            +
            import type {Properties,BooleanAttributes,DelegatedEvents,DOMElements, Mountable} from './constants.ts'
         | 
| 3 4 | 
             
            import type {Runtime,$RUNTIME} from './runtime.ts'
         | 
| 4 5 |  | 
| 5 6 | 
             
            const ELEMENT: unique symbol = Symbol(), {isArray} = Array
         | 
| @@ -89,7 +90,7 @@ export function hyperscript(r:Runtime, patch?:any):HyperScript { | |
| 89 90 | 
             
                let children: Child
         | 
| 90 91 |  | 
| 91 92 | 
             
                if (typeof second === 'object' && !isArray(second)) {
         | 
| 92 | 
            -
                  children = third || [];
         | 
| 93 | 
            +
                  children = (third as Child) || [];
         | 
| 93 94 | 
             
                  props = ((second ?? {}) as T&{children:K})
         | 
| 94 95 | 
             
                } else {
         | 
| 95 96 | 
             
                  children = (second as Child) || []
         | 
| @@ -115,7 +116,7 @@ export function hyperscript(r:Runtime, patch?:any):HyperScript { | |
| 115 116 | 
             
                      dynamicProperty(props as any, k)
         | 
| 116 117 | 
             
                    }
         | 
| 117 118 | 
             
                  }
         | 
| 118 | 
            -
                  e =  | 
| 119 | 
            +
                  e = untrack(()=>(element as Component<T&{children:K}>)(props as T&{children:K}))
         | 
| 119 120 | 
             
                  ret = () => e
         | 
| 120 121 | 
             
                } else {
         | 
| 121 122 | 
             
                  const tag = parseTag(element as Tag)
         | 
| @@ -160,12 +161,12 @@ function detectMultiExpression(list:any):boolean { | |
| 160 161 | 
             
            // ^([a-zA-Z]\w*)?(#[a-zA-Z][-\w]*)?(.[a-zA-Z][-\w]*)*
         | 
| 161 162 | 
             
            function parseTag(s:string):{name:string,id?:string,classes:string[]} {
         | 
| 162 163 | 
             
              const classes:string[] = [];
         | 
| 163 | 
            -
              let  | 
| 164 | 
            +
              let id:string|undefined = undefined, i:number
         | 
| 164 165 |  | 
| 165 166 | 
             
              i = s.indexOf('#')
         | 
| 166 167 | 
             
              if (i===-1) i = s.indexOf('.')
         | 
| 167 168 | 
             
              if (i===-1) i = s.length
         | 
| 168 | 
            -
              name = s.slice(0, i) || 'div'
         | 
| 169 | 
            +
              const name = s.slice(0, i) || 'div'
         | 
| 169 170 | 
             
              s = s.slice(i)
         | 
| 170 171 |  | 
| 171 172 | 
             
              if (s[0]==='#') {
         | 
    
        package/src/mod.ts
    CHANGED
    
    | @@ -1,15 +1,18 @@ | |
| 1 | 
            +
            // @ts-nocheck:
         | 
| 1 2 | 
             
            export type {Getter,Setter,Fn,EqualsFn,ErrorFn,RootFn,UpdateFn} from './reactive.ts'
         | 
| 2 | 
            -
            export {signal,effect, | 
| 3 | 
            +
            export {signal,effect,untrack,batch,memo,root,wrap,onMount} from './reactive.ts'
         | 
| 3 4 | 
             
            export {nbsp} from './constants.ts'
         | 
| 4 | 
            -
            export { | 
| 5 | 
            +
            export {Show,List} from './controlflow.ts'
         | 
| 5 6 | 
             
            export {runtime, type Runtime} from './runtime.ts'
         | 
| 6 7 | 
             
            import {runtime, type Runtime} from './runtime.ts'
         | 
| 7 8 | 
             
            export {hyperscript,type HyperScript,type Child,type Props,type Tag,type View,type Component} from './hyperscript.ts'
         | 
| 8 9 | 
             
            import {type HyperScript, hyperscript} from './hyperscript.ts'
         | 
| 9 | 
            -
            export { | 
| 10 | 
            -
            export  | 
| 10 | 
            +
            export {Router} from './router.js'
         | 
| 11 | 
            +
            export {resource,makeAbortable,abortable} from './resource.js'
         | 
| 12 | 
            +
            // export {Input,Table} from './components.js'
         | 
| 13 | 
            +
            // export * from './canvas.js'
         | 
| 11 14 |  | 
| 12 | 
            -
            const r:Runtime = runtime(window as any)
         | 
| 15 | 
            +
            const r:Runtime|undefined = /*#__PURE__*/ (typeof window === 'object') ? runtime(window as any) : undefined
         | 
| 13 16 |  | 
| 14 17 | 
             
            /** h
         | 
| 15 18 | 
             
            @example Element with a single child
         | 
| @@ -26,20 +29,19 @@ h(Input,{onInput:e => {}}) | |
| 26 29 | 
             
            ```
         | 
| 27 30 | 
             
            @group Hyperscript
         | 
| 28 31 | 
             
            */
         | 
| 29 | 
            -
            const h:HyperScript = hyperscript(r)
         | 
| 32 | 
            +
            const h:HyperScript|undefined = /*#__PURE__*/ r ? hyperscript(r) : undefined
         | 
| 30 33 |  | 
| 31 | 
            -
            const render = r | 
| 34 | 
            +
            const render = /*#__PURE__*/ r?.render
         | 
| 32 35 |  | 
| 33 | 
            -
            import {signal} from './reactive.ts'
         | 
| 34 | 
            -
            import {wrap} from './controlflow.js'
         | 
| 36 | 
            +
            // import {signal,wrap} from './reactive.ts'
         | 
| 35 37 |  | 
| 36 | 
            -
            /**
         | 
| 37 | 
            -
            @group Utils
         | 
| 38 | 
            -
            */
         | 
| 39 | 
            -
            export function $(a:any,b:any):any {
         | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
            }
         | 
| 38 | 
            +
            // /**
         | 
| 39 | 
            +
            // @group Utils
         | 
| 40 | 
            +
            // */
         | 
| 41 | 
            +
            // export function $(a:any,b:any):any {
         | 
| 42 | 
            +
            //   const t = typeof a
         | 
| 43 | 
            +
            //   if (t==='function') return wrap(a,b)
         | 
| 44 | 
            +
            //   else return signal(a,b)
         | 
| 45 | 
            +
            // }
         | 
| 44 46 |  | 
| 45 47 | 
             
            export {h,render}
         |