@jsweb/ui 1.2.8 → 1.3.1

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/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
 
@@ -85,6 +174,10 @@ O framework utiliza um sistema de atributos customizados para declaratividade no
85
174
 
86
175
  ### TypeScript / ESM
87
176
 
177
+ #### Abordagem com Objeto Literal (`ThisType`)
178
+
179
+ Graças ao utilitário `ThisType<T & ScopeContext>`, dentro dos métodos e _getters_ do objeto literal você tem autocomplete e tipagem estrita de `this`, incluindo as propriedades do objeto e os helpers `$refs` e `$emit`:
180
+
88
181
  ```typescript
89
182
  import { createScope, reactive, watch } from '@jsweb/ui'
90
183
 
@@ -92,17 +185,108 @@ const scope = reactive({
92
185
  count: 0,
93
186
  inc: 'Incremento',
94
187
  dec: 'Decremento',
188
+
189
+ get double() {
190
+ return this.count * 2
191
+ },
192
+
95
193
  increment() {
96
194
  this.count++
195
+ this.$emit('changed', this.count)
196
+ this.$refs.get('meuInput')?.focus()
97
197
  },
98
198
  decrement() {
99
199
  this.count--
100
200
  },
101
201
  })
102
202
 
103
- watch(() => scope.count, (newVal, oldVal) => {
104
- console.log(`Contador mudou de ${oldVal} para ${newVal}`)
105
- })
203
+ // Observa mudanças com suporte a tipagem genérica, oldValue/newValue e disparo imediato
204
+ const unwatch = watch(
205
+ () => scope.count,
206
+ (newVal, oldVal) => {
207
+ console.log(`Contador mudou de ${oldVal} para ${newVal}`)
208
+ },
209
+ { immediate: true },
210
+ )
211
+
212
+ createScope('#container', { scope })
213
+ ```
214
+
215
+ #### Abordagem Orientada a Objetos com a Classe `Scope`
216
+
217
+ Você também pode utilizar classes TypeScript estendendo a classe base `Scope` fornecida pelo framework:
218
+
219
+ ```typescript
220
+ import { createScope, reactive, Scope } from '@jsweb/ui'
221
+
222
+ class ContadorScope extends Scope {
223
+ count = 0
224
+ inc = 'Incremento'
225
+ dec = 'Decremento'
226
+
227
+ get double() {
228
+ return this.count * 2
229
+ }
230
+
231
+ increment() {
232
+ this.count++
233
+ this.$emit('changed', this.count)
234
+ this.$refs.get('meuInput')?.focus()
235
+ }
236
+
237
+ decrement() {
238
+ this.count--
239
+ }
240
+
241
+ // É possível sobrescrever o método $emit se desejar lógica customizada:
242
+ protected override $emit(event: string, detail?: any) {
243
+ console.log(`[Contador] Evento disparado: ${event}`, detail)
244
+ super.$emit(event, detail)
245
+ }
246
+ }
106
247
 
248
+ const scope = reactive(new ContadorScope())
107
249
  createScope('#container', { scope })
108
250
  ```
251
+
252
+ ## API JavaScript / TypeScript
253
+
254
+ ### `createScope<T>(selectorOrElement, context?)`
255
+
256
+ Inicializa e amarra a reatividade ao elemento DOM ou seletor especificado.
257
+
258
+ - **`selectorOrElement`**: Seletor CSS (ex: `'#app'`, `'body'`) ou instância de `HTMLElement`.
259
+ - **`context`**: Objeto inicial de contexto/estado compartilhado (opcional), tipado contextualmente com `ThisType<T & ScopeContext>`. Injeta automaticamente os helpers `$emit` e `$refs`.
260
+
261
+ ### `reactive(target)`
262
+
263
+ Envolve um objeto ou array em um `Proxy` de reatividade de grão fino (_fine-grained_).
264
+
265
+ - **Objeto Literal**: Tipado com `ThisType<T & ScopeContext>` para inferência e autocomplete de `this` (incluindo computeds via _getters_ e helpers contextuais `$refs`, `$emit` e `$el` prontos para uso sem necessidade de `?.`).
266
+ - **Instâncias de Classes**: Suporta instâncias que estendem `Scope` ou classes POJO personalizadas.
267
+ - **Arrays**: Intercepta métodos de mutação (`push`, `pop`, `splice`, etc.) e gerencia dependências de tamanho (`length`).
268
+ - **Suporta reatividade profunda** (_deep reactivity_).
269
+
270
+ ### `Scope`
271
+
272
+ Classe base utilitária para definição de escopos orientados a objetos em TypeScript. Já possui `$el` e `$refs` implementados como somente leitura e `$emit` com implementação padrão como método `protected`, permitindo sobrescrita (`override`) nas subclasses.
273
+
274
+ ```typescript
275
+ export class Scope {
276
+ readonly $el: HTMLElement
277
+ protected readonly $refs: Map<string, any>
278
+ protected $emit(event: string, detail?: any): void
279
+ declare $index?: number
280
+ declare $key?: any
281
+ constructor(init?: Record<string, any>)
282
+ }
283
+ ```
284
+
285
+ ### `watch<T>(source, callback, options?)`
286
+
287
+ Observa alterações reativas e executa uma função de callback quando o valor mudar.
288
+
289
+ - **`source`**: Objeto reativo completo ou função getter que retorna o valor a ser observado (ex: `() => state.count`).
290
+ - **`callback`**: `(newValue: T, oldValue: T | undefined) => void`.
291
+ - **`options`**: `{ immediate?: boolean }` para acionar a callback imediatamente na primeira execução.
292
+ - **Retorno**: Função de cancelamento `stop()` que encerra a observação e limpa dependências.