From b04083deabcd0fa965e47216c4c343ed057a8e9d Mon Sep 17 00:00:00 2001
From: NullSablex <244216261+NullSablex@users.noreply.github.com>
Date: Thu, 19 Feb 2026 19:38:34 -0300
Subject: [PATCH] =?UTF-8?q?feat!:=20modo=20headless,=20tipos=20TypeScript,?=
=?UTF-8?q?=20sleep,=20auto-detec=C3=A7=C3=A3o=20e=20demo=20completa=20(0.?=
=?UTF-8?q?2.0)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
BREAKING CHANGE: global UMD renomeado de `CounterUp` (namespace) para
`counterUp` (função diretamente). Uso via
```
+---
+
## API
### `counterUp(target, options)`
-`target`: seletor CSS, elemento DOM, `NodeList` ou array de elementos.
+**`target`** — o que animar:
+
+| Tipo | Exemplo | Comportamento |
+|---|---|---|
+| `string` | `"#id"`, `".classe"` | Seleciona via `document.querySelectorAll` |
+| `Element` | `document.getElementById("x")` | Usa o elemento diretamente |
+| `NodeList` / `HTMLCollection` | `document.querySelectorAll(".x")` | Anima todos os elementos |
+| `Element[]` | `[el1, el2]` | Anima todos os elementos do array |
+| `null` / `undefined` | `null` | **Modo headless** — sem DOM, use `onUpdate` |
+
+---
+
+### Opções
+
+#### Valores
+
+| Opção | Tipo | Padrão | Descrição |
+|---|---|---|---|
+| `start` | `number` | `0` | Número de onde a animação parte. O contador começa exibindo este valor. |
+| `end` | `number` | auto | Número até onde a animação conta. **Quando omitido**, a biblioteca lê o `textContent` do elemento e usa esse valor como destino — ou seja, o valor já renderizado no HTML é suficiente. Necessário em modo headless. |
+| `duration` | `number` | `2000` | Tempo total da animação em milissegundos. `0` pula diretamente para o valor de `end`. |
+
+#### Formatação
+
+| Opção | Tipo | Padrão | Descrição |
+|---|---|---|---|
+| `decimals` | `number` | auto | Quantidade de casas decimais exibidas. **Quando omitido** junto com `end`, é inferido automaticamente do `textContent` do elemento (ex.: `"15.50"` → `2`). |
+| `prefix` | `string` | `""` | Texto adicionado **antes** do número (ex.: `"R$ "`, `"$"`). |
+| `suffix` | `string` | `""` | Texto adicionado **depois** do número (ex.: `"%"`, `" pts"`). |
+| `locale` | `string` | `"pt-BR"` | Locale para `Intl.NumberFormat`. Controla separadores decimais e de milhar (ex.: `"en-US"`, `"de-DE"`). |
+| `useGrouping` | `boolean` | `true` | Exibe separador de milhar conforme o locale (`1.000` vs `1000`). |
+| `formatter` | `function \| null` | `null` | Função de formatação personalizada. Substitui toda a lógica de formatação padrão. Recebe `(value, element, index)` e deve retornar uma string. |
+
+#### Animação
+
+| Opção | Tipo | Padrão | Descrição |
+|---|---|---|---|
+| `easing` | `string \| function` | `"easeOutCubic"` | Curva de aceleração da animação. Strings aceitas: `"linear"`, `"easeInOutQuad"`, `"easeOutCubic"`. Também aceita uma função `(t: number) => number` onde `t` vai de `0` a `1`. |
+
+#### Comportamento
+
+| Opção | Tipo | Padrão | Descrição |
+|---|---|---|---|
+| `sleep` | `number` | `0` | Tempo de espera em milissegundos antes de a animação começar. `0` inicia imediatamente. Útil para escalonar múltiplos contadores ou aguardar após um elemento entrar na viewport. O sleep é cancelado se `.stop()`, `.pause()` ou `.destroy()` for chamado antes de ele disparar. |
+| `autostart` | `boolean` | `true` | Inicia a animação automaticamente ao criar a instância. Se `false`, a animação fica aguardando uma chamada manual a `.start()`. |
+| `startOnView` | `boolean` | `false` | Usa `IntersectionObserver` para iniciar a animação somente quando o elemento entra na viewport. Ignorado em modo headless (sem DOM). |
+| `once` | `boolean` | `true` | Usado com `startOnView`: se `true`, a animação dispara apenas na primeira vez que o elemento aparecer. Se `false`, reinicia toda vez que o elemento entrar na viewport. |
+
+#### IntersectionObserver (usado com `startOnView`)
+
+| Opção | Tipo | Padrão | Descrição |
+|---|---|---|---|
+| `root` | `Element \| null` | `null` | Elemento raiz do `IntersectionObserver`. `null` usa o viewport da janela. |
+| `rootMargin` | `string` | `"0px"` | Margem ao redor do root, no formato CSS (ex.: `"0px 0px -100px 0px"`). Permite disparar antes ou depois do elemento estar completamente visível. |
+| `threshold` | `number \| number[]` | `0.1` | Fração do elemento que precisa estar visível para disparar. `0.1` = 10%, `1` = 100% visível. |
+
+#### Callbacks
+
+| Opção | Tipo | Descrição |
+|---|---|---|
+| `onUpdate` | `function \| null` | Chamado a cada frame da animação com `(value, element, index)`. `element` é `null` em modo headless. |
+| `onComplete` | `function \| null` | Chamado uma vez quando a animação termina com `(value, element, index)`. `element` é `null` em modo headless. |
+
+---
+
+### Instância — elemento único
+
+#### Métodos
+
+| Método | Descrição |
+|---|---|
+| `.start()` | Inicia a animação. Se estiver pausada, retoma do ponto onde parou. Se já estiver rodando, não faz nada. |
+| `.pause()` | Pausa a animação preservando o progresso atual. |
+| `.resume()` | Retoma a animação do ponto em que foi pausada. |
+| `.stop()` | Para a animação e reseta o progresso interno (mas não o valor exibido). |
+| `.reset()` | Para a animação e volta o valor exibido para `start`. |
+| `.set(value)` | Define o valor exibido diretamente, sem animação. Para qualquer animação em curso. |
+| `.update(nextEnd, nextOptions?)` | Muda o valor final (e opcionalmente outras opções) e reinicia a animação do valor atual. |
+| `.destroy()` | Para a animação, desconecta o observer e marca a instância como destruída. Chamadas subsequentes são ignoradas. |
+
+#### Getters
+
+| Getter | Tipo | Descrição |
+|---|---|---|
+| `.value` | `number` | Valor numérico atual (sem formatação). |
+| `.running` | `boolean` | `true` se a animação estiver em execução. |
+| `.paused` | `boolean` | `true` se a animação estiver pausada. |
+| `.waiting` | `boolean` | `true` se a animação estiver aguardando o `sleep` disparar. |
-`options`:
+---
-- `start` (number, padrão `0`)
-- `end` (number, padrão `100`)
-- `duration` (number em ms, padrão `2000`)
-- `decimals` (number, padrão `0`)
-- `prefix` (string)
-- `suffix` (string)
-- `locale` (string, padrão `pt-BR`)
-- `useGrouping` (boolean, padrão `true`)
-- `easing` (`"linear"` | `"easeInOutQuad"` | `"easeOutCubic"` | function)
-- `formatter` (function)
-- `autostart` (boolean, padrão `true`)
-- `startOnView` (boolean, padrão `false`): inicia quando o elemento entra na viewport
-- `once` (boolean, padrão `true`): com `startOnView`, anima apenas uma vez
-- `root` (Element|null, padrão `null`): root do `IntersectionObserver`
-- `rootMargin` (string, padrão `"0px"`): margem do `IntersectionObserver`
-- `threshold` (number|number[], padrão `0.1`): threshold do `IntersectionObserver`
-- `onUpdate` (function)
-- `onComplete` (function)
+### Instância de grupo — múltiplos elementos
-`formatter`, `onUpdate` e `onComplete` recebem: `(value, element, index)`.
+Retornada quando `target` resolve para mais de um elemento.
-### Instância (1 elemento)
+#### Métodos
-Métodos: `start()` (inicia ou retoma se pausado), `pause()`, `resume()`, `stop()`, `reset()`, `set(value)`, `update(nextEnd, nextOptions?)`, `destroy()`
+Os mesmos da instância única, aplicados a todos os elementos:
+`start()`, `pause()`, `resume()`, `stop()`, `reset()`, `destroy()`
-Getters: `value`, `running`, `paused`
+**`set(value | value[])`** — aceita um único valor (aplicado a todos) ou um array (um valor por elemento).
-### Instância de grupo (vários elementos)
+**`update(nextEnd | nextEnd[], nextOptions?)`** — aceita um único valor final ou um array de valores finais.
-Métodos: `start()`, `pause()`, `resume()`, `stop()`, `reset()`, `set(value|array)`, `update(nextEnd|array, nextOptions?)`, `destroy()`
+#### Getters
-Getters: `values`, `running`, `paused`, `count`
+| Getter | Tipo | Descrição |
+|---|---|---|
+| `.values` | `number[]` | Array com o valor atual de cada elemento. |
+| `.running` | `boolean` | `true` se ao menos um elemento estiver animando. |
+| `.paused` | `boolean` | `true` se ao menos um elemento estiver pausado. |
+| `.waiting` | `boolean` | `true` se ao menos um elemento estiver aguardando o `sleep` disparar. |
+| `.count` | `number` | Quantidade de elementos no grupo. |
-## Build (somente para desenvolvimento da biblioteca)
+---
+
+### Exemplos de controle manual
+
+```js
+const counter = counterUp("#score", { end: 500, autostart: false });
+
+// Inicia manualmente
+counter.start();
+
+// Pausa e retoma
+counter.pause();
+counter.resume();
+
+// Muda o valor exibido sem animação
+counter.set(250);
+
+// Muda o alvo e reinicia a animação
+counter.update(1000, { duration: 800 });
+
+// Lê o valor atual em qualquer momento
+console.log(counter.value);
+
+// Libera recursos ao remover o componente
+counter.destroy();
+```
+
+---
+
+## TypeScript
+
+O pacote inclui declarações nativas em `src/counterup.d.ts`. Nenhuma instalação extra é necessária.
+
+```ts
+import { counterUp } from "@nullsablex/counter-up";
+import type {
+ CounterUpOptions,
+ CounterUpInstance,
+ CounterUpGroupInstance,
+} from "@nullsablex/counter-up";
+
+// Instância única — tipo inferido automaticamente
+const counter: CounterUpInstance = counterUp("#total", {
+ end: 1000,
+ duration: 1500,
+ prefix: "R$ ",
+ decimals: 2,
+ onComplete: (value) => console.log("Fim:", value),
+});
+
+// Modo headless — target null → CounterUpInstance garantido
+const headless: CounterUpInstance = counterUp(null, {
+ start: 0,
+ end: 100,
+ duration: 2000,
+ onUpdate: (value) => updateProgressBar(value),
+});
+
+// Opções reutilizáveis com tipagem
+const opts: CounterUpOptions = {
+ duration: 1800,
+ easing: "easeOutCubic",
+ locale: "en-US",
+};
+
+counterUp(".metric", opts);
+```
+
+### Tipos exportados
+
+| Tipo | Descrição |
+|---|---|
+| `CounterUpOptions` | Interface completa de opções |
+| `CounterUpInstance` | Instância retornada para elemento único ou headless |
+| `CounterUpGroupInstance` | Instância retornada para múltiplos elementos |
+| `CounterUpTarget` | União de todos os tipos aceitos como `target` |
+| `EasingFunction` | `(t: number) => number` |
+| `FormatterFunction` | `(value, element, index) => string` |
+| `CounterUpCallback` | Assinatura de `onUpdate` e `onComplete` |
+
+---
+
+## Build (desenvolvimento da biblioteca)
```bash
npm run build
@@ -138,20 +327,24 @@ npm run build
Arquivos gerados em `dist/`:
-- `counterup.esm.js`
-- `counterup.esm.min.js`
-- `counterup.umd.js`
-- `counterup.umd.min.js`
+- `counterup.esm.js` — ESM sem minificação
+- `counterup.esm.min.js` — ESM minificado
+- `counterup.umd.js` — UMD sem minificação
+- `counterup.umd.min.js` — UMD minificado (indicado para uso via `
+