@jsweb/ui 1.2.7 → 1.3.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,19 @@
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 ./dist --access public
@@ -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/LICENSE CHANGED
@@ -1,21 +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.
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,106 @@
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 em conjunto com a classe `ReactiveEffect`. O estado é interceptado para disparar "efeitos" com gerenciamento preciso de dependências, controle de ciclo de vida (`stop`, `cleanup`) e otimizado contra vazamento de memória.
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:** Implementado via API `watch`, permitindo reagir a mudanças em propriedades com acesso ao valor anterior/novo e disparo imediato (`immediate`).
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()` com `with(this)`.
34
+ - **Estratégia de Execução:** Para avaliar expressões declaradas no HTML de forma encapsulada (sandboxed):
35
+ 1. O motor encapsula o objeto/escopo em um Proxy de Contexto para resolução de dependências.
36
+ 2. Constrói a função dinâmica: `new Function('with(this) { ... }')`.
37
+ 3. Executa a função passando o escopo reativo atrelado ao `this`.
38
+ 4. Para eventos, também expõe a variável nativa `$event`.
39
+
40
+ ### C. Parser de Template
41
+
42
+ - **Traversal:** Utilizar `TreeWalker` ou recursão otimizada para identificar diretivas.
43
+ - **Limpeza:** Atributos `ui:*`, `ui:@*`, `@*` e `:*` devem ser removidos do DOM após a inicialização para manter o HTML limpo.
44
+
45
+ ## 4. Sintaxe e Diretivas
46
+
47
+ | Diretiva | Atalho | Descrição | Exemplo |
48
+ | :----------- | :--------- | :----------------------------------------------------------------------------------- | :----------------------------------------- |
49
+ | `ui:scope` | `:scope` | Define o objeto de estado/contexto para o elemento e seus filhos. | `<div :scope="{ count: 0 }">` |
50
+ | `ui:text` | `:text` | Sincroniza o `textContent` com uma variável ou expressão. | `<span :text="count"></span>` |
51
+ | `ui:bind` | `:bind` | Two-way data binding para inputs, checkboxes, radios, selects e textareas. | `<input :bind="name">` |
52
+ | `ui:if` | `:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div :if="count > 0">` |
53
+ | `ui:for` | `:for` | Renderiza uma lista de elementos a partir de um array (`in` ou `of`). | `<li :for="item of items">` |
54
+ | `ui:key` | `:key` | Chave de reconciliação para reaproveitamento e reciclagem de nós DOM em listas. | `<li :for="item of items" :key="item.id">` |
55
+ | `ui:class` | `:class` | Bind dinâmico para classes CSS (objeto booleano, array ou string). | `<div :class="{ active: isActive }">` |
56
+ | `ui:style` | `:style` | Bind dinâmico para estilos inline (objeto chave/valor de estilos CSS). | `<div :style="{ color: textColor }">` |
57
+ | `ui:ref` | `:ref` | Indexa elementos HTML em um Map acessível via `$refs` (suporta chaves de lista). | `<input :ref="myInput">` |
58
+ | `ui:[attr]` | `:[attr]` | Bind de atributos HTML nativos com suporte a valores booleanos (ex: disabled, href). | `<button :disabled="count > 10">` |
59
+ | `ui@[event]` | `@[event]` | Event listeners com suporte a `$event` e modificadores encadeados. | `<button @click.prevent="save">` |
60
+
61
+ ### Modificadores de Eventos
62
+
63
+ - `.prevent`: Executa `$event.preventDefault()`.
64
+ - `.stop`: Executa `$event.stopPropagation()`.
65
+ - `.self`: Executa o manipulador apenas se `$event.target === el`.
66
+ - `.outside`: Executa o manipulador quando o evento ocorre fora do elemento (com cleanup de listener no document ao desconectar o nó).
67
+
68
+ ### Helpers e Variáveis Contextuais
69
+
70
+ - `$refs`: Instância de `Map` nativa indexando elementos referenciados (elementos únicos ou Maps aninhados para itens de loops com `:key`).
71
+ - `$emit(eventName, detail?)`: Despacha CustomEvents (`bubbles: true`, `composed: true`) a partir do escopo atual.
72
+ - `$event`: Objeto nativo do evento disparado, disponível nas expressões de eventos ou repassado como 1º argumento na sintaxe de referência direta.
73
+ - `$index`: Índice numérico atual da iteração em loops `ui:for` / `:for`.
74
+
75
+ ## 5. Requisitos de Engenharia (Instruções para a IA)
76
+
77
+ - **Linguagem:** TypeScript Estrito.
78
+ - **Bundle Tool:** Vite (configurado para `build.lib` com formatos `es` e `umd`).
79
+ - **Memory Management:** Garantir o `cleanup` de event listeners e observadores quando elementos `ui:if` ou `ui:for` forem removidos.
80
+ - **Zero Dependencies:** O core não deve ter dependências externas de runtime.
81
+ - **Estilo de Código:** Funcional, modular, com comentários JSDoc claros para explicar o funcionamento interno do Proxy, do Parser e das diretivas.
82
+
83
+ ---
84
+
85
+ ### Stack de Build (Vite)
86
+
87
+ Para o `vite.config.ts`, utilize esta abordagem para satisfazer os requisitos de "Standalone" e "Module":
88
+
89
+ ```typescript
90
+ import { defineConfig } from 'vite'
91
+ import dts from 'vite-plugin-dts'
92
+
93
+ export default defineConfig({
94
+ build: {
95
+ lib: {
96
+ entry: './src/index.ts',
97
+ name: 'jswebui',
98
+ fileName: (format) => `ui.${format}.js`,
99
+ formats: ['es', 'umd'],
100
+ },
101
+ sourcemap: true,
102
+ minify: 'terser',
103
+ },
104
+ plugins: [dts()], // Gera os tipos .d.ts automaticamente
105
+ })
106
+ ```
package/README.md CHANGED
@@ -28,21 +28,110 @@ npm i @jsweb/ui
28
28
  <script src="https://unpkg.com/@jsweb/ui"></script>
29
29
  ```
30
30
 
31
- ## Diretivas Disponíveis (v0.1.0)
32
-
33
- O framework utiliza um sistema de atributos customizados para declaratividade no HTML.
34
-
35
- | Diretiva | Descrição | Exemplo |
36
- | :-------------------- | :------------------------------------------------------------------------------ | :------------------------------------ |
37
- | `ui:scope` / `:scope` | Define o objeto de estado para o elemento e seus filhos. | `<div :scope="{ count: 0 }">` |
38
- | `ui:text` / `:text` | Sincroniza o `textContent` com uma variável. | `<span :text="count"></span>` |
39
- | `:attr` | Shorthand para bind de atributos HTML nativos. | `<button :disabled="count > 10">` |
40
- | `:class` / `:style` | Bind dinâmico avançado para classes CSS e Estilos Inline (dicionários, arrays). | `<div :class="{ active: isActive }">` |
41
- | `@event` | Shorthand para event listeners (com suporte a modificadores). | `<button @click.prevent="save">` |
42
- | `$emit` | Despacha CustomEvents a partir do escopo atual. (Exposto no contexto) | `<button @click="$emit('custom')">` |
43
- | `:bind` | Two-way data binding para inputs, checkboxes, radios e selects. | `<input :bind="name">` |
44
- | `ui:if` / `:if` | Adiciona/Remove o elemento do DOM (via Comment Node placeholder). | `<div :if="count > 0">` |
45
- | `ui:for` / `:for` | Renderiza uma lista de elementos a partir de um array. | `<li :for="item in items">` |
31
+ ## Diretivas Disponíveis
32
+
33
+ O framework utiliza um sistema de atributos customizados para declaratividade no HTML, suportando tanto o prefixo completo (`ui:`, `ui@`) quanto a sintaxe simplificada (`:`, `@`).
34
+
35
+ | Diretiva | Atalho | Descrição | Exemplo |
36
+ | :----------- | :--------- | :-------------------------------------------------------------------------------- | :----------------------------------------- |
37
+ | `ui:scope` | `:scope` | Define o objeto de estado/contexto para o elemento e seus filhos. | `<div :scope="{ count: 0 }">` |
38
+ | `ui:text` | `:text` | Sincroniza o `textContent` com uma variável ou expressão. | `<span :text="count"></span>` |
39
+ | `ui:bind` | `:bind` | Two-way data binding para inputs, checkboxes, radios, selects e textarea. | `<input :bind="name">` |
40
+ | `ui:if` | `:if` | Renderização condicional no DOM (via Comment Node placeholder). | `<div :if="count > 0">` |
41
+ | `ui:for` | `:for` | Renderiza listas com suporte a `in` e `of`, expondo `$index`. | `<li :for="item of items">` |
42
+ | `ui:key` | `:key` | Identificador único para reconciliação e reciclagem eficiente de nós DOM. | `<li :for="item of items" :key="item.id">` |
43
+ | `ui:class` | `:class` | Bind reativo para classes CSS (suporta String, Array ou Objeto booleano). | `<div :class="{ active: isActive }">` |
44
+ | `ui:style` | `:style` | Bind reativo para estilos inline (recebe objeto chave/valor de propriedades CSS). | `<div :style="{ color: textColor }">` |
45
+ | `ui:ref` | `:ref` | Referencia elementos HTML indexados em um Map acessível via `$refs`. | `<input :ref="myInput">` |
46
+ | `ui:[attr]` | `:[attr]` | Bind de atributos HTML nativos (remove se falsy, ativa se booleano `true`). | `<button :disabled="count > 10">` |
47
+ | `ui@[event]` | `@[event]` | Escuta eventos DOM nativos ou customizados (com suporte a modificadores). | `<button @click.prevent="save">` |
48
+
49
+ ### Modificadores de Eventos
50
+
51
+ É possível encadear modificadores diretamente na sintaxe do evento (`@event.modificador` ou `ui@event.modificador`):
52
+
53
+ - **`.prevent`**: Executa `$event.preventDefault()`.
54
+ - **`.stop`**: Executa `$event.stopPropagation()`.
55
+ - **`.self`**: Dispara o manipulador apenas quando o evento se originou exatamente no próprio elemento (`$event.target === el`).
56
+ - **`.outside`**: Dispara quando o evento ocorre fora do elemento (ideal para fechar menus, modais e dropdowns). Gerencia o ouvinte no `document` com remoção e limpeza automáticas quando o elemento for desconectado.
57
+
58
+ ### Variáveis e Helpers de Contexto
59
+
60
+ Dentro das expressões declaradas no HTML, o framework disponibiliza variáveis e métodos contextuais:
61
+
62
+ - **`$refs`**: Objeto `Map` nativo contendo as referências registradas via `ui:ref` / `:ref`. Em elementos simples, retorna diretamente o elemento (`this.$refs.get('name')`). Em elementos dentro de loops `ui:for` com `:key`, retorna um `Map` aninhado indexado pela chave (`this.$refs.get('name').get($key)`).
63
+ - **`$emit(eventName, detail?)`**: Função injetada em todos os escopos para disparar `CustomEvent` nativos (`bubbles: true`, `composed: true`), facilitando a comunicação com elementos ancestrais (`@custom-event="handle"`).
64
+ - **`$event`**: Objeto nativo do evento disparado, disponível nas expressões de manipuladores (`@click="handle($event)"`). Se você referenciar apenas a função (`@click="handle"`), ela receberá `$event` automaticamente como primeiro argumento.
65
+ - **`$index`**: Índice numérico (base 0) da iteração atual, disponível dentro do escopo de um `ui:for` / `:for`.
66
+
67
+ ### Detalhes de Comportamento
68
+
69
+ #### Two-Way Data Binding (`ui:bind` / `:bind`)
70
+
71
+ Detecta e trata o elemento de acordo com seu tipo:
72
+
73
+ - **`input[type="checkbox"]`**: Sincroniza a propriedade booleana `checked` e atualiza no evento `change`.
74
+ - **`input[type="radio"]`**: Marca como selecionado caso `el.value === String(valor)` e atualiza no evento `change`.
75
+ - **`<select>`**: Sincroniza o `value` selecionado e escuta o evento `change`.
76
+ - **`<input>` (texto, número, cor, data, etc.) e `<textarea>`**: Sincroniza `value` e atualiza em tempo real no evento `input`.
77
+
78
+ #### Classes Dinâmicas (`ui:class` / `:class`)
79
+
80
+ Atualiza classes reativas preservando classes estáticas já presentes no elemento:
81
+
82
+ - **Objeto**: `:class="{ active: isActive, 'has-error': error }"`
83
+ - **Array**: `:class="['badge', isActive && 'badge-success']"`
84
+ - **String**: `:class="currentClass"`
85
+
86
+ #### Reconciliação Inteligente em Listas (`ui:for` / `:for` e `ui:key` / `:key`)
87
+
88
+ ```html
89
+ <ul>
90
+ <li :for="item of items" :key="item.id">
91
+ <span :text="$index"></span>: <strong :text="item.title"></strong>
92
+ </li>
93
+ </ul>
94
+ ```
95
+
96
+ Ao atualizar arrays reativos, o motor rastreia os nós pelo `:key` (ou índice por padrão) e reaproveita as instâncias existentes no DOM, evitando reflows desnecessários e mantendo estados de foco/interação.
97
+
98
+ #### Referências a Elementos (`ui:ref` / `:ref` e `$refs`)
99
+
100
+ O atributo `ui:ref` ou `:ref` indexa o elemento diretamente em um objeto `Map` acessível via `this.$refs` em métodos ou `$refs` em templates:
101
+
102
+ 1. **Uso Simples (Elemento Único)**:
103
+
104
+ ```html
105
+ <input type="text" :ref="searchBox" />
106
+ <button @click="$refs.get('searchBox').focus()">Focar</button>
107
+ ```
108
+
109
+ No código do componente:
110
+
111
+ ```javascript
112
+ this.$refs.get('searchBox').focus()
113
+ ```
114
+
115
+ 2. **Uso em Listas (`ui:for` com `:key`)**:
116
+ Quando utilizado dentro de um loop com `:key`, o framework cria automaticamente um `Map` aninhado mapeando cada elemento pela chave do item:
117
+
118
+ ```html
119
+ <ul>
120
+ <li :for="user of users" :key="user.id">
121
+ <input type="text" :value="user.name" :ref="userInput" />
122
+ </li>
123
+ </ul>
124
+ ```
125
+
126
+ Para resgatar o elemento de um item específico:
127
+
128
+ ```javascript
129
+ const input = this.$refs.get('userInput').get(user.id)
130
+ input?.focus()
131
+ ```
132
+
133
+ 3. **Limpeza Automática**:
134
+ Quando um elemento referenciado sai do DOM (seja por remoção do item em lista ou por `ui:if`), ele é desregistrado automaticamente do `Map`, evitando vazamentos de memória e referências a nós órfãos.
46
135
 
47
136
  ## Exemplo de Uso
48
137
 
@@ -100,9 +189,39 @@ const scope = reactive({
100
189
  },
101
190
  })
102
191
 
103
- watch(() => scope.count, (newVal, oldVal) => {
104
- console.log(`Contador mudou de ${oldVal} para ${newVal}`)
105
- })
192
+ // Observa mudanças com suporte a oldValue/newValue e disparo imediato opcional
193
+ const unwatch = watch(
194
+ () => scope.count,
195
+ (newVal, oldVal) => {
196
+ console.log(`Contador mudou de ${oldVal} para ${newVal}`)
197
+ },
198
+ { immediate: true },
199
+ )
106
200
 
107
201
  createScope('#container', { scope })
108
202
  ```
203
+
204
+ ## API JavaScript / TypeScript
205
+
206
+ ### `createScope(selectorOrElement, context?)`
207
+
208
+ Inicializa e amarra a reatividade ao elemento DOM ou seletor especificado.
209
+
210
+ - **`selectorOrElement`**: Seletor CSS (ex: `'#app'`, `'body'`) ou instância de `HTMLElement`.
211
+ - **`context`**: Objeto inicial de contexto/estado compartilhado (opcional). Injeta automaticamente o helper `$emit` no escopo.
212
+
213
+ ### `reactive(target)`
214
+
215
+ Envolve um objeto ou array em um `Proxy` de reatividade de grão fino (_fine-grained_).
216
+
217
+ - Suporta reatividade profunda (_deep reactivity_).
218
+ - Intercepta mutações em arrays (`push`, `pop`, `splice`, etc.) e mutações de propriedades em objetos.
219
+
220
+ ### `watch(source, callback, options?)`
221
+
222
+ Observa alterações reativas e executa uma função de callback quando o valor mudar.
223
+
224
+ - **`source`**: Objeto reativo completo ou função getter que retorna o valor a ser observado (ex: `() => state.count`).
225
+ - **`callback`**: `(newValue: any, oldValue: any) => void`.
226
+ - **`options`**: `{ immediate?: boolean }` para acionar a callback imediatamente na primeira execução.
227
+ - **Retorno**: Função de cancelamento `stop()` que encerra a observação e limpa dependências.
package/dist/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/dist/README.md ADDED
@@ -0,0 +1,227 @@
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
+ - Suporte completo a **modificadores de eventos** encadeados (`.prevent`, `.stop`, `.self`, `.outside`).
16
+
17
+ ## Instalação
18
+
19
+ ### NPM
20
+
21
+ ```bash
22
+ npm i @jsweb/ui
23
+ ```
24
+
25
+ ### CDN
26
+
27
+ ```html
28
+ <script src="https://unpkg.com/@jsweb/ui"></script>
29
+ ```
30
+
31
+ ## Diretivas Disponíveis
32
+
33
+ O framework utiliza um sistema de atributos customizados para declaratividade no HTML, suportando tanto o prefixo completo (`ui:`, `ui@`) quanto a sintaxe simplificada (`:`, `@`).
34
+
35
+ | Diretiva | Atalho | Descrição | Exemplo |
36
+ | :----------- | :--------- | :-------------------------------------------------------------------------------- | :----------------------------------------- |
37
+ | `ui:scope` | `:scope` | Define o objeto de estado/contexto para o elemento e seus filhos. | `<div :scope="{ count: 0 }">` |
38
+ | `ui:text` | `:text` | Sincroniza o `textContent` com uma variável ou expressão. | `<span :text="count"></span>` |
39
+ | `ui:bind` | `:bind` | Two-way data binding para inputs, checkboxes, radios, selects e textarea. | `<input :bind="name">` |
40
+ | `ui:if` | `:if` | Renderização condicional no DOM (via Comment Node placeholder). | `<div :if="count > 0">` |
41
+ | `ui:for` | `:for` | Renderiza listas com suporte a `in` e `of`, expondo `$index`. | `<li :for="item of items">` |
42
+ | `ui:key` | `:key` | Identificador único para reconciliação e reciclagem eficiente de nós DOM. | `<li :for="item of items" :key="item.id">` |
43
+ | `ui:class` | `:class` | Bind reativo para classes CSS (suporta String, Array ou Objeto booleano). | `<div :class="{ active: isActive }">` |
44
+ | `ui:style` | `:style` | Bind reativo para estilos inline (recebe objeto chave/valor de propriedades CSS). | `<div :style="{ color: textColor }">` |
45
+ | `ui:ref` | `:ref` | Referencia elementos HTML indexados em um Map acessível via `$refs`. | `<input :ref="myInput">` |
46
+ | `ui:[attr]` | `:[attr]` | Bind de atributos HTML nativos (remove se falsy, ativa se booleano `true`). | `<button :disabled="count > 10">` |
47
+ | `ui@[event]` | `@[event]` | Escuta eventos DOM nativos ou customizados (com suporte a modificadores). | `<button @click.prevent="save">` |
48
+
49
+ ### Modificadores de Eventos
50
+
51
+ É possível encadear modificadores diretamente na sintaxe do evento (`@event.modificador` ou `ui@event.modificador`):
52
+
53
+ - **`.prevent`**: Executa `$event.preventDefault()`.
54
+ - **`.stop`**: Executa `$event.stopPropagation()`.
55
+ - **`.self`**: Dispara o manipulador apenas quando o evento se originou exatamente no próprio elemento (`$event.target === el`).
56
+ - **`.outside`**: Dispara quando o evento ocorre fora do elemento (ideal para fechar menus, modais e dropdowns). Gerencia o ouvinte no `document` com remoção e limpeza automáticas quando o elemento for desconectado.
57
+
58
+ ### Variáveis e Helpers de Contexto
59
+
60
+ Dentro das expressões declaradas no HTML, o framework disponibiliza variáveis e métodos contextuais:
61
+
62
+ - **`$refs`**: Objeto `Map` nativo contendo as referências registradas via `ui:ref` / `:ref`. Em elementos simples, retorna diretamente o elemento (`this.$refs.get('name')`). Em elementos dentro de loops `ui:for` com `:key`, retorna um `Map` aninhado indexado pela chave (`this.$refs.get('name').get($key)`).
63
+ - **`$emit(eventName, detail?)`**: Função injetada em todos os escopos para disparar `CustomEvent` nativos (`bubbles: true`, `composed: true`), facilitando a comunicação com elementos ancestrais (`@custom-event="handle"`).
64
+ - **`$event`**: Objeto nativo do evento disparado, disponível nas expressões de manipuladores (`@click="handle($event)"`). Se você referenciar apenas a função (`@click="handle"`), ela receberá `$event` automaticamente como primeiro argumento.
65
+ - **`$index`**: Índice numérico (base 0) da iteração atual, disponível dentro do escopo de um `ui:for` / `:for`.
66
+
67
+ ### Detalhes de Comportamento
68
+
69
+ #### Two-Way Data Binding (`ui:bind` / `:bind`)
70
+
71
+ Detecta e trata o elemento de acordo com seu tipo:
72
+
73
+ - **`input[type="checkbox"]`**: Sincroniza a propriedade booleana `checked` e atualiza no evento `change`.
74
+ - **`input[type="radio"]`**: Marca como selecionado caso `el.value === String(valor)` e atualiza no evento `change`.
75
+ - **`<select>`**: Sincroniza o `value` selecionado e escuta o evento `change`.
76
+ - **`<input>` (texto, número, cor, data, etc.) e `<textarea>`**: Sincroniza `value` e atualiza em tempo real no evento `input`.
77
+
78
+ #### Classes Dinâmicas (`ui:class` / `:class`)
79
+
80
+ Atualiza classes reativas preservando classes estáticas já presentes no elemento:
81
+
82
+ - **Objeto**: `:class="{ active: isActive, 'has-error': error }"`
83
+ - **Array**: `:class="['badge', isActive && 'badge-success']"`
84
+ - **String**: `:class="currentClass"`
85
+
86
+ #### Reconciliação Inteligente em Listas (`ui:for` / `:for` e `ui:key` / `:key`)
87
+
88
+ ```html
89
+ <ul>
90
+ <li :for="item of items" :key="item.id">
91
+ <span :text="$index"></span>: <strong :text="item.title"></strong>
92
+ </li>
93
+ </ul>
94
+ ```
95
+
96
+ Ao atualizar arrays reativos, o motor rastreia os nós pelo `:key` (ou índice por padrão) e reaproveita as instâncias existentes no DOM, evitando reflows desnecessários e mantendo estados de foco/interação.
97
+
98
+ #### Referências a Elementos (`ui:ref` / `:ref` e `$refs`)
99
+
100
+ O atributo `ui:ref` ou `:ref` indexa o elemento diretamente em um objeto `Map` acessível via `this.$refs` em métodos ou `$refs` em templates:
101
+
102
+ 1. **Uso Simples (Elemento Único)**:
103
+
104
+ ```html
105
+ <input type="text" :ref="searchBox" />
106
+ <button @click="$refs.get('searchBox').focus()">Focar</button>
107
+ ```
108
+
109
+ No código do componente:
110
+
111
+ ```javascript
112
+ this.$refs.get('searchBox').focus()
113
+ ```
114
+
115
+ 2. **Uso em Listas (`ui:for` com `:key`)**:
116
+ Quando utilizado dentro de um loop com `:key`, o framework cria automaticamente um `Map` aninhado mapeando cada elemento pela chave do item:
117
+
118
+ ```html
119
+ <ul>
120
+ <li :for="user of users" :key="user.id">
121
+ <input type="text" :value="user.name" :ref="userInput" />
122
+ </li>
123
+ </ul>
124
+ ```
125
+
126
+ Para resgatar o elemento de um item específico:
127
+
128
+ ```javascript
129
+ const input = this.$refs.get('userInput').get(user.id)
130
+ input?.focus()
131
+ ```
132
+
133
+ 3. **Limpeza Automática**:
134
+ Quando um elemento referenciado sai do DOM (seja por remoção do item em lista ou por `ui:if`), ele é desregistrado automaticamente do `Map`, evitando vazamentos de memória e referências a nós órfãos.
135
+
136
+ ## Exemplo de Uso
137
+
138
+ ### HTML
139
+
140
+ ```html
141
+ <!DOCTYPE html>
142
+ <html lang="en">
143
+ <head>
144
+ <meta charset="UTF-8" />
145
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
146
+ <title>JS Web UI</title>
147
+ <script src="https://unpkg.com/@jsweb/ui"></script>
148
+ <script>
149
+ const scope = {
150
+ count: 0,
151
+ inc: 'Incremento',
152
+ dec: 'Decremento',
153
+ increment() {
154
+ this.count++
155
+ },
156
+ decrement() {
157
+ this.count--
158
+ },
159
+ }
160
+
161
+ jsweb.ui.createScope('body', { scope })
162
+ </script>
163
+ </head>
164
+ <body>
165
+ <div ui:scope="scope">
166
+ <h1>JS Web UI</h1>
167
+ <p>Contador: <span ui:text="count"></span></p>
168
+ <button ui:text="inc" @click="increment()"></button>
169
+ <button ui:text="dec" @click="decrement()"></button>
170
+ </div>
171
+ </body>
172
+ </html>
173
+ ```
174
+
175
+ ### TypeScript / ESM
176
+
177
+ ```typescript
178
+ import { createScope, reactive, watch } from '@jsweb/ui'
179
+
180
+ const scope = reactive({
181
+ count: 0,
182
+ inc: 'Incremento',
183
+ dec: 'Decremento',
184
+ increment() {
185
+ this.count++
186
+ },
187
+ decrement() {
188
+ this.count--
189
+ },
190
+ })
191
+
192
+ // Observa mudanças com suporte a oldValue/newValue e disparo imediato opcional
193
+ const unwatch = watch(
194
+ () => scope.count,
195
+ (newVal, oldVal) => {
196
+ console.log(`Contador mudou de ${oldVal} para ${newVal}`)
197
+ },
198
+ { immediate: true },
199
+ )
200
+
201
+ createScope('#container', { scope })
202
+ ```
203
+
204
+ ## API JavaScript / TypeScript
205
+
206
+ ### `createScope(selectorOrElement, context?)`
207
+
208
+ Inicializa e amarra a reatividade ao elemento DOM ou seletor especificado.
209
+
210
+ - **`selectorOrElement`**: Seletor CSS (ex: `'#app'`, `'body'`) ou instância de `HTMLElement`.
211
+ - **`context`**: Objeto inicial de contexto/estado compartilhado (opcional). Injeta automaticamente o helper `$emit` no escopo.
212
+
213
+ ### `reactive(target)`
214
+
215
+ Envolve um objeto ou array em um `Proxy` de reatividade de grão fino (_fine-grained_).
216
+
217
+ - Suporta reatividade profunda (_deep reactivity_).
218
+ - Intercepta mutações em arrays (`push`, `pop`, `splice`, etc.) e mutações de propriedades em objetos.
219
+
220
+ ### `watch(source, callback, options?)`
221
+
222
+ Observa alterações reativas e executa uma função de callback quando o valor mudar.
223
+
224
+ - **`source`**: Objeto reativo completo ou função getter que retorna o valor a ser observado (ex: `() => state.count`).
225
+ - **`callback`**: `(newValue: any, oldValue: any) => void`.
226
+ - **`options`**: `{ immediate?: boolean }` para acionar a callback imediatamente na primeira execução.
227
+ - **Retorno**: Função de cancelamento `stop()` que encerra a observação e limpa dependências.
@@ -0,0 +1,2 @@
1
+ var e=null,t=/* @__PURE__ */new WeakMap,n=/* @__PURE__ */new WeakMap,o=/* @__PURE__ */new WeakMap,s=class{fn;active=!0;deps=/* @__PURE__ */new Set;constructor(e){this.fn=e}run(){if(!this.active)return this.fn();this.cleanup(),e=Symbol(),o.set(e,this);try{return this.fn()}finally{o.delete(e),e=null}}stop(){this.active&&(this.cleanup(),this.active=!1)}cleanup(){this.deps.forEach(e=>e.delete(this)),this.deps.clear()}effect(){return{run:()=>this.run(),stop:()=>this.stop()}}};function i(e){const t=new s(e);return t.run(),t.effect()}function r(e,n){const o=t.get(e);if(!o)return;const s=o.get(n);s&&new Set(s).forEach(e=>e.run())}function c(s){if("object"!=typeof s||null===s)return s;if(s instanceof Map||s instanceof Set||s instanceof WeakMap||s instanceof WeakSet||s instanceof Date||s instanceof RegExp||"function"==typeof Node&&s instanceof Node)return s;if(Object.hasOwn(s,"_isReactive"))return s;const i=n.get(s);if(i)return i;const f=new Proxy(s,{get(n,s,i){if("_isReactive"===s)return!0;!function(n,s){if(e){let i=t.get(n);i||(i=/* @__PURE__ */new Map,t.set(n,i));let r=i.get(s);r||(r=/* @__PURE__ */new Set,i.set(s,r));const c=o.get(e);c&&(r.add(c),c.deps.add(r))}}(n,s);const r=Reflect.get(n,s,i);return"object"==typeof r&&null!==r?c(r):r},set(e,t,n,o){const s=Array.isArray(e),i=Reflect.get(e,t,o),c=s&&String(Number(t))===t?Number(t)<e.length:Object.hasOwn(e,t),f=Reflect.set(e,t,n,o);return c?i!==n&&r(e,t):(r(e,t),s&&"length"!==t&&r(e,"length")),f}});return n.set(s,f),f}function f(e,t=/* @__PURE__ */new Set){if("object"!=typeof e||null===e||t.has(e))return e;t.add(e);for(const n in e)f(e[n],t);return e}function u(e,t,n){let o,s=!0;const r=e instanceof Function?e:()=>f(e);return i(()=>{const e=r();s?(s=!1,o=e,n?.immediate&&t(e,void 0)):(t(e,o),o=e)}).stop}function a(e,t={}){try{return new Function(`with(this) { return ${e} }`).call(t)}catch{return}}function l(e,t,n={}){try{const o=t.trim(),s=/^[a-zA-Z_$][0-9a-zA-Z_$.]*$/.test(o);new Function("$event",`with(this) { ${s?`${o} instanceof Function ? ${o}.call(this, $event) : ${o}`:o} }`).call(n,e)}catch{console.warn(`[jsweb/ui] Error evaluating event: ${t}`)}}function d(e){const t=e;t._effects&&(t._effects.forEach(e=>e()),t._effects=[]);const n=Array.from(e.childNodes);for(const o of n)d(o)}function p(e,t={}){const n=e._isReactive?e:c(e);return new Proxy(n,{get:(e,n)=>"_isContext"===n||(n in e?Reflect.get(e,n,e):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):n in t?Reflect.set(t,n,o,t):Reflect.set(e,n,o,e),has:(e,n)=>n in e||n in t})}function h(e,t){if(e.nodeType!==Node.ELEMENT_NODE)return;const n=e,o=function(e,t){const n=["ui:scope",":scope"],o=b(e,n);if(!o)return t;const s=a(o,t);if(!s)return;y(e,n),s.$emit||(s.$emit=(t,n)=>{e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0}))});s.$refs||(s.$refs=t.$refs??/* @__PURE__ */new Map);return p(s,t)}(n,t);if(!o)return;const s=["ui:for",":for"],i=b(n,s);if(i)return y(n,s),void function(e,t,n){if(!e.parentNode)return;const o=/^\s*(.+)\s+(?:in|of)\s+(.+)\s*$/.exec(t);if(!o)return console.warn(`[jsweb/ui] Invalid ui:for expression: ${t}`);const[,s,i]=o,r=["ui:key",":key"],f=b(e,r);y(e,r);const u=crypto.randomUUID(),l=document.createComment(` ui:for ${u} `);e.replaceWith(l);let m=[];v(l,()=>{const t=a(i,n);if(!Array.isArray(t))return m.forEach(e=>{e.el.remove(),d(e.el)}),void(m=[]);const o=[],r=/* @__PURE__ */new Map;m.forEach(e=>r.set(e.key,e)),t.forEach((t,i)=>{let u=i;if(f){const e=p({[s]:t,$index:i},n);u=a(f,e)}const l={[s]:t,$index:i,$key:u};let d=r.get(u);if(d)d.scope[s]=t,d.scope.$index=i,d.scope.$key=u,r.delete(u);else{const t=e.cloneNode(!0),o=c(l);h(t,p(o,n)),d={key:u,el:t,scope:o}}o.push(d)}),r.forEach(e=>{e.el.remove(),d(e.el)});let u=l.nextSibling;o.forEach(e=>{u===e.el?u=u.nextSibling:l.parentNode?.insertBefore(e.el,u)}),m=o})}(n,i,o);const r=["ui:if",":if"],f=b(n,r);f&&(y(n,r),function(e,t,n){if(!e.parentNode)return;const o=crypto.randomUUID(),s=document.createComment(` ui:if ${o} `);e.before(s),v(s,()=>{a(t,n)?e.parentNode||s.parentNode?.insertBefore(e,s.nextSibling):e.parentNode&&e.remove()})}(n,f,o)),function(e,t){const n=Array.from(e.attributes);for(const o of n){const{name:n,value:s}=o,i=["ui:text",":text"].includes(n),r=["ui:bind",":bind"].includes(n),c=["ui:class",":class"].includes(n),f=["ui:style",":style"].includes(n),u=["ui:ref",":ref"].includes(n),a=n.startsWith("ui:")||n.startsWith(":"),l=n.startsWith("ui@")||n.startsWith("@");i?(w(e,s,t),e.removeAttribute(n)):r?($(e,s,t),e.removeAttribute(n)):c?(A(e,s,t),e.removeAttribute(n)):f?(S(e,s,t),e.removeAttribute(n)):u?(g(e,s,t),e.removeAttribute(n)):a?(E(e,n.split(":").pop(),s,t),e.removeAttribute(n)):l&&(x(e,n,s,t),e.removeAttribute(n))}}(n,o);const u=Array.from(n.childNodes);for(const c of u)h(c,o)}function m(e,t={}){const n="string"==typeof e?document.querySelector(e):e;n?(t.$emit||(t.$emit=(e,t)=>{n.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}),t.$refs||(t.$refs=/* @__PURE__ */new Map),h(n,t)):console.warn("[jsweb/ui] Element not found:",e)}function v(e,t){const n=i(t),o=e;o._effects??=[],o._effects.push(n.stop)}function b(e,t){for(const n of t){const t=e.getAttribute(n);if(null!==t)return t}return null}function y(e,t){for(const n of t)e.removeAttribute(n)}function g(e,t,n){const o=t.trim().replace(/^['"]|['"]$/g,"");if(!o)return;const s=n.$refs;if(!s)return;const i=n.$key;if(void 0!==i){let t=s.get(o);t instanceof Map||(t=/* @__PURE__ */new Map,s.set(o,t)),t.set(i,e)}else s.set(o,e);const r=e;r._effects??=[],r._effects.push(()=>{if(void 0!==i){const e=s.get(o);e instanceof Map&&(e.delete(i),0===e.size&&s.delete(o))}else s.get(o)===e&&s.delete(o)})}function w(e,t,n){v(e,()=>{const o=a(t,n);e.textContent=null!=o?String(o):""})}function $(e,t,n){const o=e instanceof HTMLInputElement&&"checkbox"===e.type,s=e instanceof HTMLInputElement&&"radio"===e.type;v(e,()=>{const i=a(t,n);if(o)e.checked=!!i;else if(s)e.checked=e.value===String(i);else{e.value=null==i?"":String(i)}});const i=o||s||e instanceof HTMLSelectElement?"change":"input";e.addEventListener(i,e=>{l(e,`${t} = ${"$event.target."+(o?"checked":"value")}`,n)})}function E(e,t,n,o){v(e,()=>{const s=a(n,o);null==s||!1===s?e.removeAttribute(t):!0===s?e.setAttribute(t,""):e.setAttribute(t,String(s))})}function A(e,t,n){let o=/* @__PURE__ */new Set;v(e,()=>{const s=a(t,n),i=/* @__PURE__ */new Set,r=e=>e&&i.add(e),c=e=>e.split(/\s+/).forEach(r);"string"==typeof s?c(s):Array.isArray(s)?s.flat().forEach(e=>{"string"==typeof e&&c(e)}):"object"==typeof s&&null!==s&&Object.entries(s).forEach(([e,t])=>{t&&c(e)}),o.forEach(t=>{i.has(t)||e.classList.remove(t)}),i.forEach(t=>{o.has(t)||e.classList.add(t)}),o=i})}function S(e,t,n){let o={};v(e,()=>{const s=a(t,n),i="object"==typeof s&&null!==s?s:{};for(const t in o)t in i||(e.style[t]="");for(const t in i)o[t]!==i[t]&&(e.style[t]=i[t]);o={...i}})}function x(e,t,n,o){const[s,...i]=t.split("@").pop().split("."),r=i.includes("outside"),c=r?document:e,f=t=>{if(!e.isConnected)return;const s=t.target instanceof Node;r&&s&&e.contains(t.target)||i.includes("self")&&t.target!==e||(i.includes("prevent")&&t.preventDefault(),i.includes("stop")&&t.stopPropagation(),l(t,n,o))};if(c.addEventListener(s,f),r){const t=e;t._effects??=[],t._effects.push(()=>c.removeEventListener(s,f))}}if("undefined"!=typeof window){const e=window;e.jsweb=e.jsweb||{},e.jsweb.ui={createScope:m,reactive:c,watch:u}}export{m as createScope,c as reactive,u as watch};
2
+ //# sourceMappingURL=index.es.js.map