@jsweb/ui 1.2.8 → 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.
- package/.github/workflows/npm-publish.yml +19 -0
- package/.prettierignore +3 -0
- package/.prettierrc +7 -0
- package/LICENSE +21 -21
- package/PROJECT.md +106 -0
- package/README.md +137 -18
- package/dist/LICENSE +21 -0
- package/dist/README.md +227 -0
- package/dist/index.es.js +2 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/package.json +34 -0
- package/index.html +196 -0
- package/package.json +24 -8
- package/publish.js +34 -0
- package/src/evaluator.ts +29 -0
- package/src/index.ts +10 -0
- package/src/parser.ts +480 -0
- package/src/reactivity.ts +185 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +16 -0
- package/index.es.js +0 -2
- package/index.es.js.map +0 -1
- package/index.umd.js +0 -2
- package/index.umd.js.map +0 -1
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{src → dist/src}/evaluator.d.ts +0 -0
- /package/{src → dist/src}/index.d.ts +0 -0
- /package/{src → dist/src}/parser.d.ts +0 -0
- /package/{src → dist/src}/reactivity.d.ts +0 -0
- /package/{vite.config.d.ts → dist/vite.config.d.ts} +0 -0
|
@@ -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
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
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
|
|
32
|
-
|
|
33
|
-
O framework utiliza um sistema de atributos customizados para declaratividade no HTML.
|
|
34
|
-
|
|
35
|
-
| Diretiva
|
|
36
|
-
|
|
|
37
|
-
| `ui:scope`
|
|
38
|
-
| `ui:text`
|
|
39
|
-
| `:
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
| `:
|
|
44
|
-
| `ui:
|
|
45
|
-
| `ui:
|
|
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
|
-
|
|
104
|
-
|
|
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.
|
package/dist/index.es.js
ADDED
|
@@ -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
|