@jsweb/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: NPM Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ - uses: actions/setup-node@v6
14
+ with:
15
+ node-version: 22
16
+ registry-url: https://registry.npmjs.org/
17
+ - run: npm i
18
+ - run: npm run build
19
+ - run: npm publish --access public
20
+ env:
21
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ .gemini
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": false,
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "printWidth": 80,
6
+ "overrides": [{ "files": "*.md", "options": { "parser": "markdown" } }]
7
+ }
package/HISTORY.md ADDED
@@ -0,0 +1,36 @@
1
+ # JS Web UI - Histórico de Desenvolvimento
2
+
3
+ Este documento serve como contexto histórico de todas as funcionalidades implementadas até o momento no **@jsweb/ui**, um micro-framework reativo, livre de dependências, de alta performance e sem Virtual DOM.
4
+
5
+ ## 🧠 1. Core de Reatividade (`src/reactivity.ts`)
6
+ Construímos um motor baseado em **Signals** usando `Proxy` para interceptar leituras (`track`) e escritas (`trigger`).
7
+ - **Deep Reactivity:** A reatividade funciona recursivamente em objetos profundamente aninhados.
8
+ - **Arrays e Mutabilidade:** Tratamento especial para arrays, onde a adição ou remoção de itens notifica dependências sobre a propriedade `length`, o que garante que loops reajam adequadamente a `.push`, `.pop`, etc.
9
+ - **Transparência:** O usuário final trabalha com dados mutáveis puros sem a necessidade de getters/setters explícitos (ex: `state.count++` em vez de `state.count.value++`).
10
+
11
+ ## ⚙️ 2. Motor de Avaliação (`src/evaluator.ts`)
12
+ As expressões declaradas no HTML (ex: `:text="count + 1"`) são avaliadas de forma dinâmica.
13
+ - **Execução Sandboxed Contextual:** Utilização do bloco `with(this)` dentro de `new Function` para permitir que expressões acessem propriedades do contexto diretamente, sem poluir o escopo global.
14
+ - **Passagem Implícita e Explícita de Eventos:** O método `evaluateEvent` permite o uso de `$event` explícito (ex: `@click="log($event)"`) e também mapeia automaticamente a injeção do evento caso o usuário declare apenas o nome da função (ex: `@click="log"`).
15
+
16
+ ## 🔤 3. Parser de Diretivas e DOM (`src/parser.ts`)
17
+ Em vez de Virtual DOM, manipulamos o DOM real empacotando atualizações através da função `effect`. O Parser lê o HTML e amarra os `effects`.
18
+
19
+ - **`:scope` (ou `ui:scope`):** Criação de escopos aninhados utilizando um esquema de herança de contextos (`createContext`) suportado por Proxies, permitindo "sombreamento" de propriedades corretas.
20
+ - **`:text`:** Renderização de conteúdo reativo como `textContent`.
21
+ - **`:if`:** Renderização condicional. O framework usa "âncoras" (`Comment Nodes`) para substituir dinamicamente o elemento no DOM quando a condição é falsa e restaurá-lo na posição exata quando for verdadeira.
22
+ - **`:for` e Reconciliação:** Renderização de listas utilizando algoritmo de *diffing*. O motor rastreia chaves (`:key` ou fallback para índice) de cada elemento gerado e reutiliza os mesmos nós DOM (`RenderedNode`). Isso traz performance massiva e garante que atributos nativos do navegador (ex: foco de um input) não se percam em mudanças reativas do array.
23
+ - **Atributos Genéricos (`:attr`):** Transformação dinâmica de qualquer atributo. Valores booleanos injetam/removem o atributo (ex: `disabled`).
24
+ - **Eventos (`@event`):** Adição simples de ouvintes a qualquer evento DOM nativo.
25
+ - **Two-way Data Binding (`:bind`):** Suporte total a reatividade bidirecional (Tela <-> Estado) para `input[text]`, `input[checkbox]`, `input[radio]`, `<select>` e `<textarea>`. Sincroniza em tempo real tanto via evento `input` quanto `change`.
26
+
27
+ ## 📦 4. Build e Bundling (`vite.config.ts`)
28
+ - O framework está formatado como uma biblioteca agnóstica para ser consumida como script direto ou módulo ESM via NPM.
29
+ - **Vite + Terser:** Optamos explicitamente por usar o *terser* em vez do *oxc* na etapa de minificação para atingir o nível máximo de compressão (cerca de ~1.8kB gzipped no estágio atual).
30
+ - Geração automática de pacotes de tipagem (`dts`).
31
+
32
+ ## 🎯 Próximos Passos (Backlog Futuro Sugerido)
33
+ Para outros agentes, aqui estão os próximos passos lógicos de evolução deste framework:
34
+ 1. **Modificadores de Evento:** Adicionar suporte a sufixos como `@click.prevent` e `@click.stop`.
35
+ 2. **Sintaxe Especial para Classes e Estilos:** Suporte para dicionários lógicos no CSS como `:class="{ 'is-active': active }"`.
36
+ 3. **Eventos customizados:** Implementação de um `$emit` para comunicação de um escopo/componente interno para um mais externo.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jsweb
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/PROJECT.md ADDED
@@ -0,0 +1,86 @@
1
+ # Especificação Técnica: Micro-Framework JS/TS (Codinome: @jsweb/ui)
2
+
3
+ ## 1. Visão Geral
4
+
5
+ **@jsweb/ui** é um micro-framework frontend focado em _Progressive Enhancement_ e DX (Developer Experience). Ele deve oferecer a reatividade moderna de frameworks como Vue 3 (Composition API) e a simplicidade de uso direto no HTML do Alpine.js, sem a necessidade obrigatória de um build step, mas totalmente otimizado para árvores de dependência (tree-shaking) quando usado em ambientes build-tooling.
6
+
7
+ ## 2. Pilares Arquiteturais
8
+
9
+ - **No Virtual DOM:** Utilização de reatividade de grão fino (Fine-grained reactivity) via `Proxy` ou `Signals`. Atualizações diretas no DOM real.
10
+ - **Dual Distribution:**
11
+ - **Standalone:** Arquivo único (IIFE/UMD) para inclusão via `<script src="...">`.
12
+ - **Module:** Pacote ESM com exports nomeados para suporte a Tree-Shaking.
13
+ - **Hybrid Context:** Suporte a definição de estado via Objetos Literais (POJOs) ou Classes TypeScript.
14
+ - **Template Engine:** Baseado em atributos customizados no HTML (`ui:*` para diretivas e `ui@*` para eventos, com shorthands `@`, `:`).
15
+
16
+ ## 3. Especificações do Motor (Core)
17
+
18
+ ### A. Sistema de Reatividade
19
+
20
+ - **Mecanismo:** Proxy-based. O estado deve ser interceptado para disparar "efeitos" (side-effects) que atualizam o DOM.
21
+ - **Global State:** Deve ser possível exportar um objeto reativo de um arquivo e importá-lo em múltiplos componentes/contextos, tornando-o um estado compartilhado.
22
+ - **Global Effect:** Deve ser possível criar efeitos globais que reajam a mudanças em qualquer estado compartilhado.
23
+ - **Local State:** Deve ser possível criar estados locais que reajam a mudanças apenas dentro do escopo do componente.
24
+ - **Local Effect:** Deve ser possível criar efeitos locais que reajam a mudanças apenas dentro do escopo do componente.
25
+ - **Lifecycle:** Deve ser possível criar efeitos que reajam a mudanças no ciclo de vida do componente.
26
+ - **Cleanup:** Deve ser possível limpar os efeitos quando os componentes forem removidos do DOM.
27
+ - **Watchers:** Deve ser possível criar watchers que reajam a mudanças em propriedades específicas do estado.
28
+ - **Computed:** Deve ser possível criar propriedades computadas que reajam a mudanças em propriedades específicas do estado.
29
+ - **Composition API:** Deve ser possível usar a Composition API para criar efeitos e reatividade e aninhar efeitos e reatividade em outros efeitos e reatividade.
30
+
31
+ ### B. Avaliador de Expressões (The Evaluator)
32
+
33
+ - **Implementação:** Uso de `new Function()`.
34
+ - **Estratégia de Injeção:** Para avaliar strings de atributos (ex: `ui:text="user.name"`), o motor deve:
35
+ 1. Extrair `keys` e `values` do contexto atual.
36
+ 2. Construir a função dinâmica: `new Function(...keys, 'return ' + expression)`.
37
+ 3. Executar passando os valores: `fn(...values)`.
38
+
39
+ ### C. Parser de Template
40
+
41
+ - **Traversal:** Utilizar `TreeWalker` ou recursão otimizada para identificar diretivas.
42
+ - **Limpeza:** Atributos `ui:*`, `ui:@*`, `@*` e `:*` devem ser removidos do DOM após a inicialização para manter o HTML limpo.
43
+
44
+ ## 4. Sintaxe e Diretivas (v0.1.0)
45
+
46
+ | Diretiva | Descrição | Exemplo |
47
+ | :--------- | :---------------------------------------------------------------- | :-------------------------------- |
48
+ | `ui:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div ui:scope="{ count: 0 }">` |
49
+ | `ui:text` | Sincroniza o `textContent` com uma variável. | `<span ui:text="count"></span>` |
50
+ | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
51
+ | `@event` | Shorthand para event listeners. | `<button @click="count++">` |
52
+ | `ui:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div ui:if="count > 0">` |
53
+ | `ui:for` | Renderiza uma lista de elementos a partir de um array. | `<li ui:for="item in items">` |
54
+
55
+ ## 5. Requisitos de Engenharia (Instruções para a IA)
56
+
57
+ - **Linguagem:** TypeScript Estrito.
58
+ - **Bundle Tool:** Vite (configurado para `build.lib` com formatos `es` e `umd`).
59
+ - **Memory Management:** Garantir o `cleanup` de event listeners e observadores quando elementos `ui:if` ou `ui:for` forem removidos.
60
+ - **Zero Dependencies:** O core não deve ter dependências externas de runtime.
61
+ - **Estilo de Código:** Funcional, modular, com comentários JSDoc claros para explicar o funcionamento interno do Proxy, do Parser e das diretivas.
62
+
63
+ ---
64
+
65
+ ### Stack de Build (Vite)
66
+
67
+ Para o `vite.config.ts`, utilize esta abordagem para satisfazer os requisitos de "Standalone" e "Module":
68
+
69
+ ```typescript
70
+ import { defineConfig } from 'vite'
71
+ import dts from 'vite-plugin-dts'
72
+
73
+ export default defineConfig({
74
+ build: {
75
+ lib: {
76
+ entry: './src/index.ts',
77
+ name: 'jswebui',
78
+ fileName: (format) => `ui.${format}.js`,
79
+ formats: ['es', 'umd'],
80
+ },
81
+ sourcemap: true,
82
+ minify: 'terser',
83
+ },
84
+ plugins: [dts()], // Gera os tipos .d.ts automaticamente
85
+ })
86
+ ```
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @jsweb/ui
2
+
3
+ ## Introdução
4
+
5
+ O `@jsweb/ui` é um micro-framework frontend escrito em TypeScript, projetado para ser uma ferramenta leve, rápida e flexível para o desenvolvimento de interfaces de usuário. Ele combina a reatividade moderna de frameworks como Vue 3 (Composition API) com a simplicidade de uso direto no HTML, semelhante ao Alpine.js.
6
+
7
+ ### Pilares Arquiteturais
8
+
9
+ - **Sem Virtual DOM**: Utiliza reatividade de grão fino (Fine-grained reactivity) via `Proxy` para atualizações diretas no DOM real.
10
+ - **Distribuição Dupla**:
11
+ - **Standalone**: Arquivo único (IIFE/UMD) para inclusão via `<script src="...">`.
12
+ - **Module**: Pacote ESM com exports nomeados para suporte a Tree-Shaking.
13
+ - **Contexto Híbrido**: Suporta definição de estado via Objetos Literais (POJOs) ou Classes TypeScript.
14
+ - **Template Engine**: Baseado em atributos customizados no HTML (`ui:*` para diretivas e `ui@*` para eventos, com shorthands `@`, `:`).
15
+
16
+ ## Instalação
17
+
18
+ ### NPM
19
+
20
+ ```bash
21
+ npm i @jsweb/ui
22
+ ```
23
+
24
+ ### CDN
25
+
26
+ ```html
27
+ <script src="https://unpkg.com/@jsweb/ui@latest/dist/index.umd.js"></script>
28
+ ```
29
+
30
+ ## Diretivas Disponíveis (v0.1.0)
31
+
32
+ O framework utiliza um sistema de atributos customizados para declaratividade no HTML.
33
+
34
+ | Diretiva | Descrição | Exemplo |
35
+ | :--------- | :------------------------------------------------------------------- | :-------------------------------- |
36
+ | `ui:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div ui:scope="{ count: 0 }">` |
37
+ | `ui:text` | Sincroniza o `textContent` com uma variável. | `<span ui:text="count"></span>` |
38
+ | `:attr` | Shorthand para bind de atributos HTML nativos (Binding Condicional). | `<button :disabled="count > 10">` |
39
+ | `@event` | Shorthand para event listeners. | `<button @click="count++">` |
40
+ | `ui:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div ui:if="count > 0">` |
41
+ | `ui:for` | Renderiza uma lista de elementos a partir de um array. | `<li ui:for="item in items">` |
42
+
43
+ ## Exemplo de Uso
44
+
45
+ ### HTML
46
+
47
+ ```html
48
+ <!DOCTYPE html>
49
+ <html lang="en">
50
+ <head>
51
+ <meta charset="UTF-8" />
52
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
53
+ <title>JS Web UI</title>
54
+ <script src="https://unpkg.com/@jsweb/ui@latest/dist/ui.umd.js"></script>
55
+ <script>
56
+ const scope = {
57
+ count: 0,
58
+ inc: 'Incremento',
59
+ dec: 'Decremento',
60
+ increment() {
61
+ this.count++
62
+ },
63
+ decrement() {
64
+ this.count--
65
+ },
66
+ }
67
+
68
+ jsweb.ui.createComponent('body', { scope })
69
+ </script>
70
+ </head>
71
+ <body>
72
+ <div ui:scope="scope">
73
+ <h1>JS Web UI</h1>
74
+ <p>Contador: <span ui:text="count"></span></p>
75
+ <button ui:text="inc" @click="increment()"></button>
76
+ <button ui:text="dec" @click="decrement()"></button>
77
+ </div>
78
+ </body>
79
+ </html>
80
+ ```
81
+
82
+ ### TypeScript / ESM
83
+
84
+ ```typescript
85
+ import { createComponent } from '@jsweb/ui'
86
+
87
+ const scope = {
88
+ count: 0,
89
+ inc: 'Incremento',
90
+ dec: 'Decremento',
91
+ increment() {
92
+ this.count++
93
+ },
94
+ decrement() {
95
+ this.count--
96
+ },
97
+ }
98
+
99
+ createComponent('#container', { scope })
100
+ ```
@@ -0,0 +1,2 @@
1
+ export * from './src/index'
2
+ export {}
@@ -0,0 +1,2 @@
1
+ export declare function evaluate(expression: string, context?: Record<string, any>): any;
2
+ export declare function evaluateEvent(expression: string, context: Record<string, any> | undefined, $event: Event): void;
@@ -0,0 +1,4 @@
1
+ import { reactive, effect, track, trigger } from './reactivity';
2
+ import { evaluate, evaluateEvent } from './evaluator';
3
+ import { createComponent, parseNode } from './parser';
4
+ export { reactive, effect, track, trigger, evaluate, evaluateEvent, createComponent, parseNode, };
@@ -0,0 +1,4 @@
1
+ export type Context = Record<string, any>;
2
+ export declare function createContext(scopeData: any, parentContext?: Context | null): Context;
3
+ export declare function parseNode(node: Node, context: Context): void;
4
+ export declare function createComponent(selectorOrElement: string | HTMLElement, rootContext?: Context): void;
@@ -0,0 +1,4 @@
1
+ export declare function effect(fn: () => void): void;
2
+ export declare function track(target: object, key: string | symbol): void;
3
+ export declare function trigger(target: object, key: string | symbol): void;
4
+ export declare function reactive<T extends object>(target: T): T;
package/dist/ui.es.js ADDED
@@ -0,0 +1,2 @@
1
+ var e=null,t=/* @__PURE__ */new WeakMap;function n(t){const n=()=>{e=n,t(),e=null};n()}function r(n,r){if(e){let o=t.get(n);o||(o=/* @__PURE__ */new Map,t.set(n,o));let i=o.get(r);i||(i=/* @__PURE__ */new Set,o.set(r,i)),i.add(e)}}function o(e,n){const r=t.get(e);if(!r)return;const o=r.get(n);o&&o.forEach(e=>e())}function i(e){return"object"!=typeof e||null===e||e.__isReactive?e:new Proxy(e,{get(e,t,n){if("__isReactive"===t)return!0;r(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?i(o):o},set(e,t,n,r){const i=Array.isArray(e),c=Reflect.get(e,t,r),s=i&&String(Number(t))===t?Number(t)<e.length:Object.prototype.hasOwnProperty.call(e,t),u=Reflect.set(e,t,n,r);return s?c!==n&&o(e,t):(o(e,t),i&&"length"!==t&&o(e,"length")),u}})}function c(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function s(e,t={},n){try{const r=e.trim(),o=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(r);new Function("$event",`with(this) { ${o?`${r} instanceof Function ? ${r}.call(this, $event) : ${r}`:r} }`).call(t,n)}catch(r){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,r)}}function u(e,t=null){const n=e.__isReactive?e:i(e);return new Proxy(n,{get:(e,n)=>"__isContext"===n||(n in e?Reflect.get(e,n,e):t&&n in t?Reflect.get(t,n,t):Reflect.get(e,n,e)),set:(e,n,r)=>n in e?Reflect.set(e,n,r,e):t&&n in t?Reflect.set(t,n,r,t):Reflect.set(e,n,r,e),has:(e,n)=>n in e||!(!t||!(n in t))})}function l(e,t){if(e.nodeType===Node.ELEMENT_NODE){const r=e;let o=t;const a=r.getAttribute("ui:scope")||r.getAttribute(":scope");a&&(o=u(c(a,t)||{},t),r.removeAttribute("ui:scope"),r.removeAttribute(":scope"));const d=r.getAttribute("ui:for")||r.getAttribute(":for");if(d)return r.removeAttribute("ui:for"),r.removeAttribute(":for"),void function(e,t,r){const o=e.parentNode;if(!o)return;const s=t.match(/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/);if(!s)return void console.warn(`[jsweb/ui] Invalid ui:for expression: ${t}`);const[,a,f]=s,d=e.getAttribute("ui:key")||e.getAttribute(":key");e.removeAttribute("ui:key"),e.removeAttribute(":key");const p=crypto.randomUUID(),b=document.createComment(` ui:for ${p} `);o.replaceChild(b,e);let v=[];n(()=>{const t=c(f,r);if(!Array.isArray(t))return v.forEach(e=>e.el.parentNode?.removeChild(e.el)),void(v=[]);const n=[],o=/* @__PURE__ */new Map;v.forEach(e=>o.set(e.key,e)),t.forEach((t,s)=>{const f={[a]:t,$index:s};let p=s;d&&(p=c(d,u(f,r)));let b=o.get(p);if(b)b.scope[a]=t,b.scope.$index=s,o.delete(p);else{const t=e.cloneNode(!0),n=i(f);l(t,u(n,r)),b={key:p,el:t,scope:n}}n.push(b)}),o.forEach(e=>{e.el.parentNode?.removeChild(e.el)});let s=b.nextSibling;n.forEach(e=>{s===e.el?s=s.nextSibling:b.parentNode?.insertBefore(e.el,s)}),v=n})}(r,d,o);const p=r.getAttribute("ui:if")||r.getAttribute(":if");p&&(r.removeAttribute("ui:if"),r.removeAttribute(":if"),function(e,t,r){const o=e.parentNode;if(!o)return;const i=crypto.randomUUID(),s=document.createComment(` ui:if ${i} `);o.insertBefore(s,e),n(()=>{c(t,r)?e.parentNode||s.parentNode?.insertBefore(e,s.nextSibling):e.parentNode&&e.parentNode.removeChild(e)})}(r,p,o));const b=Array.from(r.attributes);for(const e of b){const{name:t,value:i}=e,u=["ui:text",":text"].includes(t),l=["ui:bind",":bind"].includes(t),a=t.startsWith("ui:")||t.startsWith(":"),d=t.startsWith("ui@")||t.startsWith("@");if(u)r.removeAttribute(t),n(()=>{const e=c(i,o);r.textContent=null!=e?String(e):""});else if(l)r.removeAttribute(t),f(r,i,o);else if(a){const e=t.split(":").pop();r.removeAttribute(t),n(()=>{const t=c(i,o);null==t||!1===t?r.removeAttribute(e):!0===t?r.setAttribute(e,""):r.setAttribute(e,String(t))})}else if(d){const e=t.split("@").pop();r.removeAttribute(t),r.addEventListener(e,e=>{s(i,o,e)})}}const v=Array.from(r.childNodes);for(const e of v)l(e,o)}}function a(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?l(n,t):console.warn(`[jsweb/ui] Element not found: ${e}`)}function f(e,t,r){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,i=e instanceof HTMLInputElement&&"radio"===e.type;n(()=>{const n=c(t,r);if(o){e.checked=!!n}else if(i){const t=e;t.checked=t.value===String(n)}else{e.value=null==n?"":String(n)}});const u=o||i||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(u,e=>{s(o?`${t} = $event.target.checked`:`${t} = $event.target.value`,r,e)})}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createComponent:a,reactive:i,effect:n,track:r,trigger:o,evaluate:c,evaluateEvent:s,parseNode:l}}export{a as createComponent,n as effect,c as evaluate,s as evaluateEvent,l as parseNode,i as reactive,r as track,o as trigger};
2
+ //# sourceMappingURL=ui.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.es.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: (() => void) | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<() => void>>>()\n\nexport function effect(fn: () => void) {\n const effectFn = () => {\n // cleanup old deps could be added here\n activeEffect = effectFn\n fn()\n activeEffect = null\n }\n effectFn()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n dep.add(activeEffect)\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const dep = depsMap.get(key)\n if (dep) {\n dep.forEach((effectFn) => effectFn())\n }\n}\n\nexport function reactive<T extends object>(target: T): T {\n if (typeof target !== 'object' || target === null) return target\n if ((target as any).__isReactive) return target\n\n return new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '__isReactive') return true\n track(obj, key)\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey = isArray && String(Number(key)) === key \n ? Number(key) < obj.length \n : Object.prototype.hasOwnProperty.call(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n \n return result\n },\n })\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\n }\n}\n","import { effect, reactive } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\nexport function createContext(scopeData: any, parentContext: Context | null = null): Context {\n const reactiveScope = scopeData.__isReactive ? scopeData : reactive(scopeData)\n \n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '__isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (parentContext && prop in parentContext) {\n return Reflect.get(parentContext, prop, parentContext)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (parentContext && prop in parentContext) {\n return Reflect.set(parentContext, prop, value, parentContext)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (parentContext && prop in parentContext) return true\n return false\n }\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as HTMLElement\n\n // 1. Check for scope\n let currentContext = context\n const scopeAttr = el.getAttribute('ui:scope') || el.getAttribute(':scope')\n if (scopeAttr) {\n const scopeData = evaluate(scopeAttr, context) || {}\n currentContext = createContext(scopeData, context)\n el.removeAttribute('ui:scope')\n el.removeAttribute(':scope')\n }\n\n // 2. Check for ui:for (must be processed before children and other directives on same element)\n const forAttr = el.getAttribute('ui:for') || el.getAttribute(':for')\n if (forAttr) {\n el.removeAttribute('ui:for')\n el.removeAttribute(':for')\n processFor(el, forAttr, currentContext)\n return // Stop processing this node further, processFor handles clones\n }\n\n // 3. Check for ui:if\n const ifAttr = el.getAttribute('ui:if') || el.getAttribute(':if')\n if (ifAttr) {\n el.removeAttribute('ui:if')\n el.removeAttribute(':if')\n processIf(el, ifAttr, currentContext)\n // We continue processing children because the element might be shown\n }\n\n // 4. Other directives\n const attrs = Array.from(el.attributes)\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':') \n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n el.removeAttribute(name)\n effect(() => {\n const val = evaluate(value, currentContext)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n } else if (isTwoWayBind) {\n el.removeAttribute(name)\n processTwoWayBinding(el, value, currentContext)\n } else if (isAttrBind) {\n const boundAttr = name.split(':').pop()! \n el.removeAttribute(name)\n effect(() => {\n const val = evaluate(value, currentContext)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(boundAttr)\n } else if (val === true) {\n el.setAttribute(boundAttr, '')\n } else {\n el.setAttribute(boundAttr, String(val))\n }\n })\n } else if (isEvent) {\n const eventName = name.split('@').pop()!\n el.removeAttribute(name)\n el.addEventListener(eventName, ($event) => {\n evaluateEvent(value, currentContext, $event)\n })\n }\n }\n\n // Process children\n // Need to convert to array because childNodes might mutate if elements are added/removed\n const children = Array.from(el.childNodes)\n for (const child of children) {\n parseNode(child, currentContext)\n }\n }\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n parent.insertBefore(comment, el)\n\n effect(() => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else {\n if (el.parentNode) {\n el.parentNode.removeChild(el)\n }\n }\n })\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n \n const match = expr.match(/^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/)\n if (!match) {\n console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n return\n }\n const [, itemName, listName] = match\n \n const keyExpr = el.getAttribute('ui:key') || el.getAttribute(':key')\n el.removeAttribute('ui:key')\n el.removeAttribute(':key')\n \n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n parent.replaceChild(comment, el)\n \n type RenderedNode = {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n effect(() => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach(node => node.el.parentNode?.removeChild(node.el))\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach(node => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n const scope = { [itemName]: item, $index: index }\n let key: any = index\n\n if (keyExpr) {\n const tempContext = createContext(scope, context)\n key = evaluate(keyExpr, tempContext)\n }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n \n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach(node => {\n node.el.parentNode?.removeChild(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nexport function createComponent(\n selectorOrElement: string | HTMLElement,\n rootContext: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n parseNode(el, rootContext)\n } else {\n console.warn(`[jsweb/ui] Element not found: ${selectorOrElement}`)\n }\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n \n // 1. Reactive state to DOM\n effect(() => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n const target = el as HTMLInputElement\n target.checked = !!val\n } else if (isRadio) {\n const target = el as HTMLInputElement\n target.checked = target.value === String(val)\n } else {\n const target = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\n })\n}\n","import { reactive, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createComponent, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createComponent,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createComponent,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"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"}
package/dist/ui.umd.js ADDED
@@ -0,0 +1,2 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@jsweb/ui"]={})}(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var t=null,n=new WeakMap;function o(e){const n=()=>{t=n,e(),t=null};n()}function r(e,o){if(t){let r=n.get(e);r||(r=new Map,n.set(e,r));let i=r.get(o);i||(i=new Set,r.set(o,i)),i.add(t)}}function i(e,t){const o=n.get(e);if(!o)return;const r=o.get(t);r&&r.forEach(e=>e())}function c(e){return"object"!=typeof e||null===e||e.__isReactive?e:new Proxy(e,{get(e,t,n){if("__isReactive"===t)return!0;r(e,t);const o=Reflect.get(e,t,n);return"object"==typeof o&&null!==o?c(o):o},set(e,t,n,o){const r=Array.isArray(e),c=Reflect.get(e,t,o),s=r&&String(Number(t))===t?Number(t)<e.length:Object.prototype.hasOwnProperty.call(e,t),u=Reflect.set(e,t,n,o);return s?c!==n&&i(e,t):(i(e,t),r&&"length"!==t&&i(e,"length")),u}})}function s(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch(n){return void console.error(`[jsweb/ui] Error evaluating expression: ${e}`,n)}}function u(e,t={},n){try{const o=e.trim(),r=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${r?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(t,n)}catch(o){console.error(`[jsweb/ui] Error evaluating event expression: ${e}`,o)}}function l(e,t=null){const n=e.__isReactive?e:c(e);return new Proxy(n,{get:(e,n)=>"__isContext"===n||(n in e?Reflect.get(e,n,e):t&&n in t?Reflect.get(t,n,t):Reflect.get(e,n,e)),set:(e,n,o)=>n in e?Reflect.set(e,n,o,e):t&&n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||!(!t||!(n in t))})}function a(e,t){if(e.nodeType===Node.ELEMENT_NODE){const n=e;let r=t;const i=n.getAttribute("ui:scope")||n.getAttribute(":scope");i&&(r=l(s(i,t)||{},t),n.removeAttribute("ui:scope"),n.removeAttribute(":scope"));const f=n.getAttribute("ui:for")||n.getAttribute(":for");if(f)return n.removeAttribute("ui:for"),n.removeAttribute(":for"),void function(e,t,n){const r=e.parentNode;if(!r)return;const i=t.match(/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/);if(!i)return void console.warn(`[jsweb/ui] Invalid ui:for expression: ${t}`);const[,u,f]=i,d=e.getAttribute("ui:key")||e.getAttribute(":key");e.removeAttribute("ui:key"),e.removeAttribute(":key");const p=crypto.randomUUID(),b=document.createComment(` ui:for ${p} `);r.replaceChild(b,e);let v=[];o(()=>{const t=s(f,n);if(!Array.isArray(t))return v.forEach(e=>e.el.parentNode?.removeChild(e.el)),void(v=[]);const o=[],r=new Map;v.forEach(e=>r.set(e.key,e)),t.forEach((t,i)=>{const f={[u]:t,$index:i};let p=i;d&&(p=s(d,l(f,n)));let b=r.get(p);if(b)b.scope[u]=t,b.scope.$index=i,r.delete(p);else{const t=e.cloneNode(!0),o=c(f);a(t,l(o,n)),b={key:p,el:t,scope:o}}o.push(b)}),r.forEach(e=>{e.el.parentNode?.removeChild(e.el)});let i=b.nextSibling;o.forEach(e=>{i===e.el?i=i.nextSibling:b.parentNode?.insertBefore(e.el,i)}),v=o})}(n,f,r);const p=n.getAttribute("ui:if")||n.getAttribute(":if");p&&(n.removeAttribute("ui:if"),n.removeAttribute(":if"),function(e,t,n){const r=e.parentNode;if(!r)return;const i=crypto.randomUUID(),c=document.createComment(` ui:if ${i} `);r.insertBefore(c,e),o(()=>{s(t,n)?e.parentNode||c.parentNode?.insertBefore(e,c.nextSibling):e.parentNode&&e.parentNode.removeChild(e)})}(n,p,r));const b=Array.from(n.attributes);for(const e of b){const{name:t,value:i}=e,c=["ui:text",":text"].includes(t),l=["ui:bind",":bind"].includes(t),a=t.startsWith("ui:")||t.startsWith(":"),f=t.startsWith("ui@")||t.startsWith("@");if(c)n.removeAttribute(t),o(()=>{const e=s(i,r);n.textContent=null!=e?String(e):""});else if(l)n.removeAttribute(t),d(n,i,r);else if(a){const e=t.split(":").pop();n.removeAttribute(t),o(()=>{const t=s(i,r);null==t||!1===t?n.removeAttribute(e):!0===t?n.setAttribute(e,""):n.setAttribute(e,String(t))})}else if(f){const e=t.split("@").pop();n.removeAttribute(t),n.addEventListener(e,e=>{u(i,r,e)})}}const v=Array.from(n.childNodes);for(const e of v)a(e,r)}}function f(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?a(n,t):console.warn(`[jsweb/ui] Element not found: ${e}`)}function d(e,t,n){const r=e instanceof HTMLInputElement&&"checkbox"===e.type,i=e instanceof HTMLInputElement&&"radio"===e.type;o(()=>{const o=s(t,n);if(r){e.checked=!!o}else if(i){const t=e;t.checked=t.value===String(o)}else{e.value=null==o?"":String(o)}});const c=r||i||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(c,e=>{u(r?`${t} = $event.target.checked`:`${t} = $event.target.value`,n,e)})}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createComponent:f,reactive:c,effect:o,track:r,trigger:i,evaluate:s,evaluateEvent:u,parseNode:a}}e.createComponent=f,e.effect=o,e.evaluate=s,e.evaluateEvent=u,e.parseNode=a,e.reactive=c,e.track=r,e.trigger=i});
2
+ //# sourceMappingURL=ui.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.umd.js","names":[],"sources":["../src/reactivity.ts","../src/evaluator.ts","../src/parser.ts","../src/index.ts"],"sourcesContent":["let activeEffect: (() => void) | null = null\nconst targetMap = new WeakMap<object, Map<string | symbol, Set<() => void>>>()\n\nexport function effect(fn: () => void) {\n const effectFn = () => {\n // cleanup old deps could be added here\n activeEffect = effectFn\n fn()\n activeEffect = null\n }\n effectFn()\n}\n\nexport function track(target: object, key: string | symbol) {\n if (activeEffect) {\n let depsMap = targetMap.get(target)\n if (!depsMap) {\n depsMap = new Map()\n targetMap.set(target, depsMap)\n }\n let dep = depsMap.get(key)\n if (!dep) {\n dep = new Set()\n depsMap.set(key, dep)\n }\n dep.add(activeEffect)\n }\n}\n\nexport function trigger(target: object, key: string | symbol) {\n const depsMap = targetMap.get(target)\n if (!depsMap) return\n const dep = depsMap.get(key)\n if (dep) {\n dep.forEach((effectFn) => effectFn())\n }\n}\n\nexport function reactive<T extends object>(target: T): T {\n if (typeof target !== 'object' || target === null) return target\n if ((target as any).__isReactive) return target\n\n return new Proxy(target, {\n get(obj, key, receiver) {\n if (key === '__isReactive') return true\n track(obj, key)\n const res = Reflect.get(obj, key, receiver)\n // deep reactivity\n if (typeof res === 'object' && res !== null) {\n return reactive(res)\n }\n return res\n },\n set(obj, key, value, receiver) {\n const isArray = Array.isArray(obj)\n const oldValue = Reflect.get(obj, key, receiver)\n const hadKey = isArray && String(Number(key)) === key \n ? Number(key) < obj.length \n : Object.prototype.hasOwnProperty.call(obj, key)\n\n const result = Reflect.set(obj, key, value, receiver)\n\n if (!hadKey) {\n trigger(obj, key)\n if (isArray && key !== 'length') {\n trigger(obj, 'length')\n }\n } else if (oldValue !== value) {\n trigger(obj, key)\n }\n \n return result\n },\n })\n}\n","export function evaluate(\n expression: string,\n context: Record<string, any> = {},\n) {\n try {\n const fn = new Function(`with(this) { return ${expression} }`)\n return fn.call(context)\n } catch (error) {\n console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)\n return undefined\n }\n}\n\nexport function evaluateEvent(\n expression: string,\n context: Record<string, any> = {},\n $event: Event,\n) {\n try {\n const exp = expression.trim()\n const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)\n const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`\n const result = isIdentifier ? code : exp\n\n const fn = new Function('$event', `with(this) { ${result} }`)\n fn.call(context, $event)\n } catch (error) {\n console.error(\n `[jsweb/ui] Error evaluating event expression: ${expression}`,\n error,\n )\n }\n}\n","import { effect, reactive } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\n\nexport type Context = Record<string, any>\n\nexport function createContext(scopeData: any, parentContext: Context | null = null): Context {\n const reactiveScope = scopeData.__isReactive ? scopeData : reactive(scopeData)\n \n return new Proxy(reactiveScope, {\n get(target, prop) {\n if (prop === '__isContext') return true\n if (prop in target) return Reflect.get(target, prop, target)\n if (parentContext && prop in parentContext) {\n return Reflect.get(parentContext, prop, parentContext)\n }\n return Reflect.get(target, prop, target)\n },\n set(target, prop, value) {\n if (prop in target) return Reflect.set(target, prop, value, target)\n if (parentContext && prop in parentContext) {\n return Reflect.set(parentContext, prop, value, parentContext)\n }\n return Reflect.set(target, prop, value, target)\n },\n has(target, prop) {\n if (prop in target) return true\n if (parentContext && prop in parentContext) return true\n return false\n }\n })\n}\n\nexport function parseNode(node: Node, context: Context) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as HTMLElement\n\n // 1. Check for scope\n let currentContext = context\n const scopeAttr = el.getAttribute('ui:scope') || el.getAttribute(':scope')\n if (scopeAttr) {\n const scopeData = evaluate(scopeAttr, context) || {}\n currentContext = createContext(scopeData, context)\n el.removeAttribute('ui:scope')\n el.removeAttribute(':scope')\n }\n\n // 2. Check for ui:for (must be processed before children and other directives on same element)\n const forAttr = el.getAttribute('ui:for') || el.getAttribute(':for')\n if (forAttr) {\n el.removeAttribute('ui:for')\n el.removeAttribute(':for')\n processFor(el, forAttr, currentContext)\n return // Stop processing this node further, processFor handles clones\n }\n\n // 3. Check for ui:if\n const ifAttr = el.getAttribute('ui:if') || el.getAttribute(':if')\n if (ifAttr) {\n el.removeAttribute('ui:if')\n el.removeAttribute(':if')\n processIf(el, ifAttr, currentContext)\n // We continue processing children because the element might be shown\n }\n\n // 4. Other directives\n const attrs = Array.from(el.attributes)\n for (const attr of attrs) {\n const { name, value } = attr\n const isText = ['ui:text', ':text'].includes(name)\n const isTwoWayBind = ['ui:bind', ':bind'].includes(name)\n const isAttrBind = name.startsWith('ui:') || name.startsWith(':') \n const isEvent = name.startsWith('ui@') || name.startsWith('@')\n\n if (isText) {\n el.removeAttribute(name)\n effect(() => {\n const val = evaluate(value, currentContext)\n el.textContent = val !== undefined && val !== null ? String(val) : ''\n })\n } else if (isTwoWayBind) {\n el.removeAttribute(name)\n processTwoWayBinding(el, value, currentContext)\n } else if (isAttrBind) {\n const boundAttr = name.split(':').pop()! \n el.removeAttribute(name)\n effect(() => {\n const val = evaluate(value, currentContext)\n if (val === null || val === undefined || val === false) {\n el.removeAttribute(boundAttr)\n } else if (val === true) {\n el.setAttribute(boundAttr, '')\n } else {\n el.setAttribute(boundAttr, String(val))\n }\n })\n } else if (isEvent) {\n const eventName = name.split('@').pop()!\n el.removeAttribute(name)\n el.addEventListener(eventName, ($event) => {\n evaluateEvent(value, currentContext, $event)\n })\n }\n }\n\n // Process children\n // Need to convert to array because childNodes might mutate if elements are added/removed\n const children = Array.from(el.childNodes)\n for (const child of children) {\n parseNode(child, currentContext)\n }\n }\n}\n\nfunction processIf(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n\n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:if ${uuid} `)\n parent.insertBefore(comment, el)\n\n effect(() => {\n const val = evaluate(expr, context)\n if (val) {\n if (!el.parentNode) {\n comment.parentNode?.insertBefore(el, comment.nextSibling)\n }\n } else {\n if (el.parentNode) {\n el.parentNode.removeChild(el)\n }\n }\n })\n}\n\nfunction processFor(el: HTMLElement, expr: string, context: Context) {\n const parent = el.parentNode\n if (!parent) return\n \n const match = expr.match(/^\\s*(.+)\\s+(?:in|of)\\s+(.+)\\s*$/)\n if (!match) {\n console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)\n return\n }\n const [, itemName, listName] = match\n \n const keyExpr = el.getAttribute('ui:key') || el.getAttribute(':key')\n el.removeAttribute('ui:key')\n el.removeAttribute(':key')\n \n const uuid = crypto.randomUUID()\n const comment = document.createComment(` ui:for ${uuid} `)\n parent.replaceChild(comment, el)\n \n type RenderedNode = {\n key: any\n el: HTMLElement\n scope: any\n }\n let renderedNodes: RenderedNode[] = []\n\n effect(() => {\n const list = evaluate(listName, context)\n\n if (!Array.isArray(list)) {\n renderedNodes.forEach(node => node.el.parentNode?.removeChild(node.el))\n renderedNodes = []\n return\n }\n\n const newNodes: RenderedNode[] = []\n const oldNodesByKey = new Map<any, RenderedNode>()\n renderedNodes.forEach(node => oldNodesByKey.set(node.key, node))\n\n list.forEach((item, index) => {\n const scope = { [itemName]: item, $index: index }\n let key: any = index\n\n if (keyExpr) {\n const tempContext = createContext(scope, context)\n key = evaluate(keyExpr, tempContext)\n }\n\n let node = oldNodesByKey.get(key)\n if (node) {\n // Reuse node\n node.scope[itemName] = item\n node.scope.$index = index\n oldNodesByKey.delete(key)\n } else {\n // Create new node\n const clone = el.cloneNode(true) as HTMLElement\n const reactiveScope = reactive(scope)\n const localContext = createContext(reactiveScope, context)\n parseNode(clone, localContext)\n node = { key, el: clone, scope: reactiveScope }\n }\n \n newNodes.push(node)\n })\n\n // Remove un-reused nodes\n oldNodesByKey.forEach(node => {\n node.el.parentNode?.removeChild(node.el)\n })\n\n // Reorder and insert new DOM nodes\n let currentAnchor = comment.nextSibling\n newNodes.forEach((node) => {\n if (currentAnchor === node.el) {\n currentAnchor = currentAnchor.nextSibling\n } else {\n comment.parentNode?.insertBefore(node.el, currentAnchor)\n }\n })\n\n renderedNodes = newNodes\n })\n}\n\nexport function createComponent(\n selectorOrElement: string | HTMLElement,\n rootContext: Context = {},\n) {\n const el =\n typeof selectorOrElement === 'string'\n ? document.querySelector(selectorOrElement)\n : selectorOrElement\n\n if (el) {\n parseNode(el, rootContext)\n } else {\n console.warn(`[jsweb/ui] Element not found: ${selectorOrElement}`)\n }\n}\n\nfunction processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {\n const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'\n const isRadio = el instanceof HTMLInputElement && el.type === 'radio'\n \n // 1. Reactive state to DOM\n effect(() => {\n const val = evaluate(expr, context)\n if (isCheckbox) {\n const target = el as HTMLInputElement\n target.checked = !!val\n } else if (isRadio) {\n const target = el as HTMLInputElement\n target.checked = target.value === String(val)\n } else {\n const target = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement\n target.value = val == null ? '' : String(val)\n }\n })\n\n // 2. DOM to Reactive state\n const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement\n const eventName = isChange ? 'change' : 'input'\n el.addEventListener(eventName, ($event) => {\n if (isCheckbox) {\n evaluateEvent(`${expr} = $event.target.checked`, context, $event)\n } else {\n evaluateEvent(`${expr} = $event.target.value`, context, $event)\n }\n })\n}\n","import { reactive, effect, track, trigger } from './reactivity'\nimport { evaluate, evaluateEvent } from './evaluator'\nimport { createComponent, parseNode } from './parser'\n\nexport {\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n createComponent,\n parseNode,\n}\n\nif (typeof window !== 'undefined') {\n const w = window as any\n w.jsweb = w.jsweb || {}\n w.jsweb.ui = {\n createComponent,\n reactive,\n effect,\n track,\n trigger,\n evaluate,\n evaluateEvent,\n parseNode,\n }\n}\n"],"mappings":"mSAAA,IAAI,EAAoC,KAClC,EAAY,IAAI,QAEtB,SAAgB,EAAO,GACrB,MAAM,EAAA,KAEJ,EAAe,EACf,IACA,EAAe,MAEjB,IAGF,SAAgB,EAAM,EAAgB,GACpC,GAAI,EAAc,CAChB,IAAI,EAAU,EAAU,IAAI,GACvB,IACH,EAAU,IAAI,IACd,EAAU,IAAI,EAAQ,IAExB,IAAI,EAAM,EAAQ,IAAI,GACjB,IACH,EAAM,IAAI,IACV,EAAQ,IAAI,EAAK,IAEnB,EAAI,IAAI,IAIZ,SAAgB,EAAQ,EAAgB,GACtC,MAAM,EAAU,EAAU,IAAI,GAC9B,IAAK,EAAS,OACd,MAAM,EAAM,EAAQ,IAAI,GACpB,GACF,EAAI,QAAS,GAAa,KAI9B,SAAgB,EAA2B,GACzC,MAAsB,iBAAX,GAAkC,OAAX,GAC7B,EAAe,aADsC,EAGnD,IAAI,MAAM,EAAQ,CACvB,GAAA,CAAI,EAAK,EAAK,GACZ,GAAY,iBAAR,EAAwB,OAAO,EACnC,EAAM,EAAK,GACX,MAAM,EAAM,QAAQ,IAAI,EAAK,EAAK,GAElC,MAAmB,iBAAR,GAA4B,OAAR,EACtB,EAAS,GAEX,GAET,GAAA,CAAI,EAAK,EAAK,EAAO,GACnB,MAAM,EAAU,MAAM,QAAQ,GACxB,EAAW,QAAQ,IAAI,EAAK,EAAK,GACjC,EAAS,GAAW,OAAO,OAAO,MAAU,EAC9C,OAAO,GAAO,EAAI,OAClB,OAAO,UAAU,eAAe,KAAK,EAAK,GAExC,EAAS,QAAQ,IAAI,EAAK,EAAK,EAAO,GAW5C,OATK,EAKM,IAAa,GACtB,EAAQ,EAAK,IALb,EAAQ,EAAK,GACT,GAAmB,WAAR,GACb,EAAQ,EAAK,WAMV,KCvEb,SAAgB,EACd,EACA,EAA+B,CAAA,GAE/B,IAEE,OAAO,IADQ,SAAS,uBAAuB,OACrC,KAAK,SACR,GAEP,YADA,QAAQ,MAAM,2CAA2C,IAAc,IAK3E,SAAgB,EACd,EACA,EAA+B,CAAA,EAC/B,GAEA,IACE,MAAM,EAAM,EAAW,OACjB,EAAe,8BAA8B,KAAK,GAKxD,IADe,SAAS,SAAU,gBAFnB,EADF,GAAG,2BAA6B,0BAA4B,IACpC,OAGlC,KAAK,EAAS,SACV,GACP,QAAQ,MACN,iDAAiD,IACjD,ICxBN,SAAgB,EAAc,EAAgB,EAAgC,MAC5E,MAAM,EAAgB,EAAU,aAAe,EAAY,EAAS,GAEpE,OAAO,IAAI,MAAM,EAAe,CAC9B,IAAA,CAAI,EAAQ,IACG,gBAAT,IACA,KAAQ,EAAe,QAAQ,IAAI,EAAQ,EAAM,GACjD,GAAiB,KAAQ,EACpB,QAAQ,IAAI,EAAe,EAAM,GAEnC,QAAQ,IAAI,EAAQ,EAAM,IAEnC,IAAA,CAAI,EAAQ,EAAM,IACZ,KAAQ,EAAe,QAAQ,IAAI,EAAQ,EAAM,EAAO,GACxD,GAAiB,KAAQ,EACpB,QAAQ,IAAI,EAAe,EAAM,EAAO,GAE1C,QAAQ,IAAI,EAAQ,EAAM,EAAO,GAE1C,IAAA,CAAI,EAAQ,IACN,KAAQ,MACR,KAAiB,KAAQ,MAMnC,SAAgB,EAAU,EAAY,GACpC,GAAI,EAAK,WAAa,KAAK,aAAc,CACvC,MAAM,EAAK,EAGX,IAAI,EAAiB,EACrB,MAAM,EAAY,EAAG,aAAa,aAAe,EAAG,aAAa,UAC7D,IAEF,EAAiB,EADC,EAAS,EAAW,IAAY,CAAA,EACR,GAC1C,EAAG,gBAAgB,YACnB,EAAG,gBAAgB,WAIrB,MAAM,EAAU,EAAG,aAAa,WAAa,EAAG,aAAa,QAC7D,GAAI,EAIF,OAHA,EAAG,gBAAgB,UACnB,EAAG,gBAAgB,aAqFzB,SAAoB,EAAiB,EAAc,GACjD,MAAM,EAAS,EAAG,WAClB,IAAK,EAAQ,OAEb,MAAM,EAAQ,EAAK,MAAM,mCACzB,IAAK,EAEH,YADA,QAAQ,KAAK,yCAAyC,KAGxD,MAAM,CAAG,EAAU,GAAY,EAEzB,EAAU,EAAG,aAAa,WAAa,EAAG,aAAa,QAC7D,EAAG,gBAAgB,UACnB,EAAG,gBAAgB,QAEnB,MAAM,EAAO,OAAO,aACd,EAAU,SAAS,cAAc,WAAW,MAClD,EAAO,aAAa,EAAS,GAO7B,IAAI,EAAgC,GAEpC,EAAA,KACE,MAAM,EAAO,EAAS,EAAU,GAEhC,IAAK,MAAM,QAAQ,GAGjB,OAFA,EAAc,QAAQ,GAAQ,EAAK,GAAG,YAAY,YAAY,EAAK,UACnE,EAAgB,IAIlB,MAAM,EAA2B,GAC3B,EAAgB,IAAI,IAC1B,EAAc,QAAQ,GAAQ,EAAc,IAAI,EAAK,IAAK,IAE1D,EAAK,QAAA,CAAS,EAAM,KAClB,MAAM,EAAQ,EAAG,GAAW,EAAM,OAAQ,GAC1C,IAAI,EAAW,EAEX,IAEF,EAAM,EAAS,EADK,EAAc,EAAO,KAI3C,IAAI,EAAO,EAAc,IAAI,GAC7B,GAAI,EAEF,EAAK,MAAM,GAAY,EACvB,EAAK,MAAM,OAAS,EACpB,EAAc,OAAO,OAChB,CAEL,MAAM,EAAQ,EAAG,WAAU,GACrB,EAAgB,EAAS,GAE/B,EAAU,EADW,EAAc,EAAe,IAElD,EAAO,CAAE,MAAK,GAAI,EAAO,MAAO,GAGlC,EAAS,KAAK,KAIhB,EAAc,QAAQ,IACpB,EAAK,GAAG,YAAY,YAAY,EAAK,MAIvC,IAAI,EAAgB,EAAQ,YAC5B,EAAS,QAAS,IACZ,IAAkB,EAAK,GACzB,EAAgB,EAAc,YAE9B,EAAQ,YAAY,aAAa,EAAK,GAAI,KAI9C,EAAgB,IArKd,CAAW,EAAI,EAAS,GAK1B,MAAM,EAAS,EAAG,aAAa,UAAY,EAAG,aAAa,OACvD,IACF,EAAG,gBAAgB,SACnB,EAAG,gBAAgB,OAsDzB,SAAmB,EAAiB,EAAc,GAChD,MAAM,EAAS,EAAG,WAClB,IAAK,EAAQ,OAEb,MAAM,EAAO,OAAO,aACd,EAAU,SAAS,cAAc,UAAU,MACjD,EAAO,aAAa,EAAS,GAE7B,EAAA,KACc,EAAS,EAAM,GAEpB,EAAG,YACN,EAAQ,YAAY,aAAa,EAAI,EAAQ,aAG3C,EAAG,YACL,EAAG,WAAW,YAAY,KArE5B,CAAU,EAAI,EAAQ,IAKxB,MAAM,EAAQ,MAAM,KAAK,EAAG,YAC5B,IAAK,MAAM,KAAQ,EAAO,CACxB,MAAM,KAAE,EAAA,MAAM,GAAU,EAClB,EAAS,CAAC,UAAW,SAAS,SAAS,GACvC,EAAe,CAAC,UAAW,SAAS,SAAS,GAC7C,EAAa,EAAK,WAAW,QAAU,EAAK,WAAW,KACvD,EAAU,EAAK,WAAW,QAAU,EAAK,WAAW,KAE1D,GAAI,EACF,EAAG,gBAAgB,GACnB,EAAA,KACE,MAAM,EAAM,EAAS,EAAO,GAC5B,EAAG,YAAc,QAAoC,OAAO,GAAO,aAE5D,EACT,EAAG,gBAAgB,GACnB,EAAqB,EAAI,EAAO,WACvB,EAAY,CACrB,MAAM,EAAY,EAAK,MAAM,KAAK,MAClC,EAAG,gBAAgB,GACnB,EAAA,KACE,MAAM,EAAM,EAAS,EAAO,GACxB,UAA6C,IAAR,EACvC,EAAG,gBAAgB,IACF,IAAR,EACT,EAAG,aAAa,EAAW,IAE3B,EAAG,aAAa,EAAW,OAAO,cAG7B,EAAS,CAClB,MAAM,EAAY,EAAK,MAAM,KAAK,MAClC,EAAG,gBAAgB,GACnB,EAAG,iBAAiB,EAAY,IAC9B,EAAc,EAAO,EAAgB,MAO3C,MAAM,EAAW,MAAM,KAAK,EAAG,YAC/B,IAAK,MAAM,KAAS,EAClB,EAAU,EAAO,IAgHvB,SAAgB,EACd,EACA,EAAuB,CAAA,GAEvB,MAAM,EACyB,iBAAtB,EACH,SAAS,cAAc,GACvB,EAEF,EACF,EAAU,EAAI,GAEd,QAAQ,KAAK,iCAAiC,KAIlD,SAAS,EAAqB,EAAiB,EAAc,GAC3D,MAAM,EAAa,aAAc,kBAAgC,aAAZ,EAAG,KAClD,EAAU,aAAc,kBAAgC,UAAZ,EAAG,KAGrD,EAAA,KACE,MAAM,EAAM,EAAS,EAAM,GAC3B,GAAI,EAAY,CACC,EACR,UAAY,UACV,EAAS,CAClB,MAAM,EAAS,EACf,EAAO,QAAU,EAAO,QAAU,OAAO,OACpC,CACU,EACR,MAAe,MAAP,EAAc,GAAK,OAAO,MAM7C,MAAM,EADW,GAAc,GAAW,aAAc,kBAC3B,SAAW,QACxC,EAAG,iBAAiB,EAAY,IAE5B,EADE,EACY,GAAG,4BAEH,GAAG,0BAFgC,EAAS,KCrPhE,GAAsB,oBAAX,OAAwB,CACjC,MAAM,EAAI,OACV,EAAE,MAAQ,EAAE,OAAS,CAAA,EACrB,EAAE,MAAM,GAAK,CACX,kBACA,WACA,SACA,QACA,UACA,WACA,gBACA"}
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vite').UserConfig;
2
+ export default _default;
package/index.html ADDED
@@ -0,0 +1,87 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>JS Web UI Test</title>
7
+ <script type="module">
8
+ import { createComponent, reactive } from '/src/index.ts'
9
+
10
+ const scope = reactive({
11
+ count: 0,
12
+ inc: 'Incremento',
13
+ dec: 'Decremento',
14
+ model: 'Exemplo',
15
+ items: ['A', 'B', 'C'],
16
+
17
+ get computedItems() {
18
+ return this.items.map((value, index) => {
19
+ return { value, index }
20
+ })
21
+ },
22
+
23
+ increment() {
24
+ this.count++
25
+ },
26
+ decrement() {
27
+ this.count--
28
+ },
29
+ zero() {
30
+ this.count = 0
31
+ },
32
+ addItem() {
33
+ const value = Date.now()
34
+ this.items.push(value)
35
+ },
36
+ removeItem() {
37
+ this.items.pop()
38
+ },
39
+ logEvent(e, ...args) {
40
+ console.log('Evento recebido:', e, args)
41
+ this.items.push(`Evento ${e.type}`)
42
+ }
43
+ })
44
+
45
+ window.scope = scope
46
+
47
+ createComponent('body', { scope })
48
+ </script>
49
+ </head>
50
+ <body>
51
+ <div :scope="scope">
52
+ <h1>JS Web UI</h1>
53
+ <p>Contador: <span :text="count"></span></p>
54
+ <button :text="inc" @click="increment">+</button>
55
+ <button :text="dec" @click="decrement">-</button>
56
+
57
+ <div style="margin-top: 20px">
58
+ <button @click="zero" :disabled="!count">Zerar</button>
59
+ </div>
60
+
61
+ <div
62
+ :if="count > 0"
63
+ style="margin-top: 20px; padding: 10px; border: 1px solid green"
64
+ >
65
+ O contador é maior que zero!
66
+ </div>
67
+
68
+ <div style="margin-top: 20px">
69
+ <h3>Lista:</h3>
70
+ <div style="margin-bottom: 10px;">
71
+ <input type="text" :bind="model" placeholder="Digite algo..." />
72
+ <p>Você vai adicionar: <strong :text="model"></strong></p>
73
+ <button @click="addItem">Adicionar Item</button>
74
+ <button @click="removeItem">Remover Item</button>
75
+ <button @click="logEvent">Testar Evento (Sem Parênteses)</button>
76
+ <button @click="logEvent($event, 'A', 'B', 'C')">Testar Evento (Com Parênteses)</button>
77
+ </div>
78
+ <ul>
79
+ <li :for="item of computedItems">
80
+ <span :text="item.index"></span>
81
+ <input type="text" :value="item.value" />
82
+ </li>
83
+ </ul>
84
+ </div>
85
+ </div>
86
+ </body>
87
+ </html>
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@jsweb/ui",
3
+ "version": "0.1.0",
4
+ "description": "JS Web Microframework",
5
+ "keywords": [
6
+ "js",
7
+ "ts",
8
+ "web",
9
+ "ui",
10
+ "micro",
11
+ "framework"
12
+ ],
13
+ "homepage": "https://github.com/jsweb/ui#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/jsweb/ui/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/jsweb/ui.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Alex Bruno Cáceres <email@alexbruno.dev>",
23
+ "type": "module",
24
+ "main": "dist/ui.umd.js",
25
+ "module": "dist/ui.es.js",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/ui.es.js",
30
+ "require": "./dist/ui.umd.js",
31
+ "types": "./dist/index.d.ts"
32
+ }
33
+ },
34
+ "scripts": {
35
+ "dev": "vite",
36
+ "build": "tsc && vite build",
37
+ "preview": "vite preview",
38
+ "format": "prettier --write .",
39
+ "test": "echo 'test'",
40
+ "postversion": "git push && git push --tags"
41
+ },
42
+ "devDependencies": {
43
+ "prettier": "^3.8.3",
44
+ "terser": "^5.46.2",
45
+ "typescript": "^6.0.3",
46
+ "vite": "^8.0.10",
47
+ "vite-plugin-dts": "^5.0.0"
48
+ }
49
+ }
@@ -0,0 +1,33 @@
1
+ export function evaluate(
2
+ expression: string,
3
+ context: Record<string, any> = {},
4
+ ) {
5
+ try {
6
+ const fn = new Function(`with(this) { return ${expression} }`)
7
+ return fn.call(context)
8
+ } catch (error) {
9
+ console.error(`[jsweb/ui] Error evaluating expression: ${expression}`, error)
10
+ return undefined
11
+ }
12
+ }
13
+
14
+ export function evaluateEvent(
15
+ expression: string,
16
+ context: Record<string, any> = {},
17
+ $event: Event,
18
+ ) {
19
+ try {
20
+ const exp = expression.trim()
21
+ const isIdentifier = /^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(exp)
22
+ const code = `${exp} instanceof Function ? ${exp}.call(this, $event) : ${exp}`
23
+ const result = isIdentifier ? code : exp
24
+
25
+ const fn = new Function('$event', `with(this) { ${result} }`)
26
+ fn.call(context, $event)
27
+ } catch (error) {
28
+ console.error(
29
+ `[jsweb/ui] Error evaluating event expression: ${expression}`,
30
+ error,
31
+ )
32
+ }
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { reactive, effect, track, trigger } from './reactivity'
2
+ import { evaluate, evaluateEvent } from './evaluator'
3
+ import { createComponent, parseNode } from './parser'
4
+
5
+ export {
6
+ reactive,
7
+ effect,
8
+ track,
9
+ trigger,
10
+ evaluate,
11
+ evaluateEvent,
12
+ createComponent,
13
+ parseNode,
14
+ }
15
+
16
+ if (typeof window !== 'undefined') {
17
+ const w = window as any
18
+ w.jsweb = w.jsweb || {}
19
+ w.jsweb.ui = {
20
+ createComponent,
21
+ reactive,
22
+ effect,
23
+ track,
24
+ trigger,
25
+ evaluate,
26
+ evaluateEvent,
27
+ parseNode,
28
+ }
29
+ }
package/src/parser.ts ADDED
@@ -0,0 +1,266 @@
1
+ import { effect, reactive } from './reactivity'
2
+ import { evaluate, evaluateEvent } from './evaluator'
3
+
4
+ export type Context = Record<string, any>
5
+
6
+ export function createContext(scopeData: any, parentContext: Context | null = null): Context {
7
+ const reactiveScope = scopeData.__isReactive ? scopeData : reactive(scopeData)
8
+
9
+ return new Proxy(reactiveScope, {
10
+ get(target, prop) {
11
+ if (prop === '__isContext') return true
12
+ if (prop in target) return Reflect.get(target, prop, target)
13
+ if (parentContext && prop in parentContext) {
14
+ return Reflect.get(parentContext, prop, parentContext)
15
+ }
16
+ return Reflect.get(target, prop, target)
17
+ },
18
+ set(target, prop, value) {
19
+ if (prop in target) return Reflect.set(target, prop, value, target)
20
+ if (parentContext && prop in parentContext) {
21
+ return Reflect.set(parentContext, prop, value, parentContext)
22
+ }
23
+ return Reflect.set(target, prop, value, target)
24
+ },
25
+ has(target, prop) {
26
+ if (prop in target) return true
27
+ if (parentContext && prop in parentContext) return true
28
+ return false
29
+ }
30
+ })
31
+ }
32
+
33
+ export function parseNode(node: Node, context: Context) {
34
+ if (node.nodeType === Node.ELEMENT_NODE) {
35
+ const el = node as HTMLElement
36
+
37
+ // 1. Check for scope
38
+ let currentContext = context
39
+ const scopeAttr = el.getAttribute('ui:scope') || el.getAttribute(':scope')
40
+ if (scopeAttr) {
41
+ const scopeData = evaluate(scopeAttr, context) || {}
42
+ currentContext = createContext(scopeData, context)
43
+ el.removeAttribute('ui:scope')
44
+ el.removeAttribute(':scope')
45
+ }
46
+
47
+ // 2. Check for ui:for (must be processed before children and other directives on same element)
48
+ const forAttr = el.getAttribute('ui:for') || el.getAttribute(':for')
49
+ if (forAttr) {
50
+ el.removeAttribute('ui:for')
51
+ el.removeAttribute(':for')
52
+ processFor(el, forAttr, currentContext)
53
+ return // Stop processing this node further, processFor handles clones
54
+ }
55
+
56
+ // 3. Check for ui:if
57
+ const ifAttr = el.getAttribute('ui:if') || el.getAttribute(':if')
58
+ if (ifAttr) {
59
+ el.removeAttribute('ui:if')
60
+ el.removeAttribute(':if')
61
+ processIf(el, ifAttr, currentContext)
62
+ // We continue processing children because the element might be shown
63
+ }
64
+
65
+ // 4. Other directives
66
+ const attrs = Array.from(el.attributes)
67
+ for (const attr of attrs) {
68
+ const { name, value } = attr
69
+ const isText = ['ui:text', ':text'].includes(name)
70
+ const isTwoWayBind = ['ui:bind', ':bind'].includes(name)
71
+ const isAttrBind = name.startsWith('ui:') || name.startsWith(':')
72
+ const isEvent = name.startsWith('ui@') || name.startsWith('@')
73
+
74
+ if (isText) {
75
+ el.removeAttribute(name)
76
+ effect(() => {
77
+ const val = evaluate(value, currentContext)
78
+ el.textContent = val !== undefined && val !== null ? String(val) : ''
79
+ })
80
+ } else if (isTwoWayBind) {
81
+ el.removeAttribute(name)
82
+ processTwoWayBinding(el, value, currentContext)
83
+ } else if (isAttrBind) {
84
+ const boundAttr = name.split(':').pop()!
85
+ el.removeAttribute(name)
86
+ effect(() => {
87
+ const val = evaluate(value, currentContext)
88
+ if (val === null || val === undefined || val === false) {
89
+ el.removeAttribute(boundAttr)
90
+ } else if (val === true) {
91
+ el.setAttribute(boundAttr, '')
92
+ } else {
93
+ el.setAttribute(boundAttr, String(val))
94
+ }
95
+ })
96
+ } else if (isEvent) {
97
+ const eventName = name.split('@').pop()!
98
+ el.removeAttribute(name)
99
+ el.addEventListener(eventName, ($event) => {
100
+ evaluateEvent(value, currentContext, $event)
101
+ })
102
+ }
103
+ }
104
+
105
+ // Process children
106
+ // Need to convert to array because childNodes might mutate if elements are added/removed
107
+ const children = Array.from(el.childNodes)
108
+ for (const child of children) {
109
+ parseNode(child, currentContext)
110
+ }
111
+ }
112
+ }
113
+
114
+ function processIf(el: HTMLElement, expr: string, context: Context) {
115
+ const parent = el.parentNode
116
+ if (!parent) return
117
+
118
+ const uuid = crypto.randomUUID()
119
+ const comment = document.createComment(` ui:if ${uuid} `)
120
+ parent.insertBefore(comment, el)
121
+
122
+ effect(() => {
123
+ const val = evaluate(expr, context)
124
+ if (val) {
125
+ if (!el.parentNode) {
126
+ comment.parentNode?.insertBefore(el, comment.nextSibling)
127
+ }
128
+ } else {
129
+ if (el.parentNode) {
130
+ el.parentNode.removeChild(el)
131
+ }
132
+ }
133
+ })
134
+ }
135
+
136
+ function processFor(el: HTMLElement, expr: string, context: Context) {
137
+ const parent = el.parentNode
138
+ if (!parent) return
139
+
140
+ const match = expr.match(/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/)
141
+ if (!match) {
142
+ console.warn(`[jsweb/ui] Invalid ui:for expression: ${expr}`)
143
+ return
144
+ }
145
+ const [, itemName, listName] = match
146
+
147
+ const keyExpr = el.getAttribute('ui:key') || el.getAttribute(':key')
148
+ el.removeAttribute('ui:key')
149
+ el.removeAttribute(':key')
150
+
151
+ const uuid = crypto.randomUUID()
152
+ const comment = document.createComment(` ui:for ${uuid} `)
153
+ parent.replaceChild(comment, el)
154
+
155
+ type RenderedNode = {
156
+ key: any
157
+ el: HTMLElement
158
+ scope: any
159
+ }
160
+ let renderedNodes: RenderedNode[] = []
161
+
162
+ effect(() => {
163
+ const list = evaluate(listName, context)
164
+
165
+ if (!Array.isArray(list)) {
166
+ renderedNodes.forEach(node => node.el.parentNode?.removeChild(node.el))
167
+ renderedNodes = []
168
+ return
169
+ }
170
+
171
+ const newNodes: RenderedNode[] = []
172
+ const oldNodesByKey = new Map<any, RenderedNode>()
173
+ renderedNodes.forEach(node => oldNodesByKey.set(node.key, node))
174
+
175
+ list.forEach((item, index) => {
176
+ const scope = { [itemName]: item, $index: index }
177
+ let key: any = index
178
+
179
+ if (keyExpr) {
180
+ const tempContext = createContext(scope, context)
181
+ key = evaluate(keyExpr, tempContext)
182
+ }
183
+
184
+ let node = oldNodesByKey.get(key)
185
+ if (node) {
186
+ // Reuse node
187
+ node.scope[itemName] = item
188
+ node.scope.$index = index
189
+ oldNodesByKey.delete(key)
190
+ } else {
191
+ // Create new node
192
+ const clone = el.cloneNode(true) as HTMLElement
193
+ const reactiveScope = reactive(scope)
194
+ const localContext = createContext(reactiveScope, context)
195
+ parseNode(clone, localContext)
196
+ node = { key, el: clone, scope: reactiveScope }
197
+ }
198
+
199
+ newNodes.push(node)
200
+ })
201
+
202
+ // Remove un-reused nodes
203
+ oldNodesByKey.forEach(node => {
204
+ node.el.parentNode?.removeChild(node.el)
205
+ })
206
+
207
+ // Reorder and insert new DOM nodes
208
+ let currentAnchor = comment.nextSibling
209
+ newNodes.forEach((node) => {
210
+ if (currentAnchor === node.el) {
211
+ currentAnchor = currentAnchor.nextSibling
212
+ } else {
213
+ comment.parentNode?.insertBefore(node.el, currentAnchor)
214
+ }
215
+ })
216
+
217
+ renderedNodes = newNodes
218
+ })
219
+ }
220
+
221
+ export function createComponent(
222
+ selectorOrElement: string | HTMLElement,
223
+ rootContext: Context = {},
224
+ ) {
225
+ const el =
226
+ typeof selectorOrElement === 'string'
227
+ ? document.querySelector(selectorOrElement)
228
+ : selectorOrElement
229
+
230
+ if (el) {
231
+ parseNode(el, rootContext)
232
+ } else {
233
+ console.warn(`[jsweb/ui] Element not found: ${selectorOrElement}`)
234
+ }
235
+ }
236
+
237
+ function processTwoWayBinding(el: HTMLElement, expr: string, context: Context) {
238
+ const isCheckbox = el instanceof HTMLInputElement && el.type === 'checkbox'
239
+ const isRadio = el instanceof HTMLInputElement && el.type === 'radio'
240
+
241
+ // 1. Reactive state to DOM
242
+ effect(() => {
243
+ const val = evaluate(expr, context)
244
+ if (isCheckbox) {
245
+ const target = el as HTMLInputElement
246
+ target.checked = !!val
247
+ } else if (isRadio) {
248
+ const target = el as HTMLInputElement
249
+ target.checked = target.value === String(val)
250
+ } else {
251
+ const target = el as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
252
+ target.value = val == null ? '' : String(val)
253
+ }
254
+ })
255
+
256
+ // 2. DOM to Reactive state
257
+ const isChange = isCheckbox || isRadio || el instanceof HTMLSelectElement
258
+ const eventName = isChange ? 'change' : 'input'
259
+ el.addEventListener(eventName, ($event) => {
260
+ if (isCheckbox) {
261
+ evaluateEvent(`${expr} = $event.target.checked`, context, $event)
262
+ } else {
263
+ evaluateEvent(`${expr} = $event.target.value`, context, $event)
264
+ }
265
+ })
266
+ }
@@ -0,0 +1,75 @@
1
+ let activeEffect: (() => void) | null = null
2
+ const targetMap = new WeakMap<object, Map<string | symbol, Set<() => void>>>()
3
+
4
+ export function effect(fn: () => void) {
5
+ const effectFn = () => {
6
+ // cleanup old deps could be added here
7
+ activeEffect = effectFn
8
+ fn()
9
+ activeEffect = null
10
+ }
11
+ effectFn()
12
+ }
13
+
14
+ export function track(target: object, key: string | symbol) {
15
+ if (activeEffect) {
16
+ let depsMap = targetMap.get(target)
17
+ if (!depsMap) {
18
+ depsMap = new Map()
19
+ targetMap.set(target, depsMap)
20
+ }
21
+ let dep = depsMap.get(key)
22
+ if (!dep) {
23
+ dep = new Set()
24
+ depsMap.set(key, dep)
25
+ }
26
+ dep.add(activeEffect)
27
+ }
28
+ }
29
+
30
+ export function trigger(target: object, key: string | symbol) {
31
+ const depsMap = targetMap.get(target)
32
+ if (!depsMap) return
33
+ const dep = depsMap.get(key)
34
+ if (dep) {
35
+ dep.forEach((effectFn) => effectFn())
36
+ }
37
+ }
38
+
39
+ export function reactive<T extends object>(target: T): T {
40
+ if (typeof target !== 'object' || target === null) return target
41
+ if ((target as any).__isReactive) return target
42
+
43
+ return new Proxy(target, {
44
+ get(obj, key, receiver) {
45
+ if (key === '__isReactive') return true
46
+ track(obj, key)
47
+ const res = Reflect.get(obj, key, receiver)
48
+ // deep reactivity
49
+ if (typeof res === 'object' && res !== null) {
50
+ return reactive(res)
51
+ }
52
+ return res
53
+ },
54
+ set(obj, key, value, receiver) {
55
+ const isArray = Array.isArray(obj)
56
+ const oldValue = Reflect.get(obj, key, receiver)
57
+ const hadKey = isArray && String(Number(key)) === key
58
+ ? Number(key) < obj.length
59
+ : Object.prototype.hasOwnProperty.call(obj, key)
60
+
61
+ const result = Reflect.set(obj, key, value, receiver)
62
+
63
+ if (!hadKey) {
64
+ trigger(obj, key)
65
+ if (isArray && key !== 'length') {
66
+ trigger(obj, 'length')
67
+ }
68
+ } else if (oldValue !== value) {
69
+ trigger(obj, key)
70
+ }
71
+
72
+ return result
73
+ },
74
+ })
75
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["src", "vite.config.ts"]
23
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vite'
2
+ import dts from 'vite-plugin-dts'
3
+
4
+ export default defineConfig({
5
+ build: {
6
+ lib: {
7
+ name: '@jsweb/ui',
8
+ formats: ['es', 'umd'],
9
+ entry: './src/index.ts',
10
+ fileName: (format: string) => `ui.${format}.js`,
11
+ },
12
+ sourcemap: true,
13
+ minify: 'terser',
14
+ },
15
+ plugins: [dts({ insertTypesEntry: true })],
16
+ })