@sevn/elections-components 1.0.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/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/index.cjs +603 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +351 -0
- package/dist/index.d.ts +351 -0
- package/dist/index.js +15609 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +38 -0
- package/dist/umd/candidate-by-state.js +98 -0
- package/dist/umd/candidate-carrousel-state-results-viewer.js +108 -0
- package/dist/umd/candidate-counting-progress.js +77 -0
- package/dist/umd/candidate-duel-state-result-viewer.js +91 -0
- package/dist/umd/candidate-map-by-state.js +104 -0
- package/dist/umd/candidate-multi-ring-chart-performance-by-municipality.js +105 -0
- package/dist/umd/candidate-performance-by-municipality.js +105 -0
- package/dist/umd/candidate-ranking.js +59 -0
- package/dist/umd/candidate-results-by-municipality.js +103 -0
- package/dist/umd/counting-progress.js +125 -0
- package/dist/umd/current-leader.js +43 -0
- package/dist/umd/election-round-analysis.js +100 -0
- package/dist/umd/election-status.js +135 -0
- package/dist/umd/final-results.js +141 -0
- package/dist/umd/live-summary.js +36 -0
- package/dist/umd/municipality-perfomance-map.js +82 -0
- package/dist/umd/party-performance.js +99 -0
- package/dist/umd/results-by-office.js +94 -0
- package/dist/umd/round-results-analysis.js +99 -0
- package/dist/umd/state-leaders.js +118 -0
- package/dist/umd/state-winners.js +129 -0
- package/dist/umd/top-dispute.js +43 -0
- package/dist/umd/vote-comparison-by-round.js +76 -0
- package/dist/umd/vote-counting-progress.js +23 -0
- package/dist/umd/voting-map.js +112 -0
- package/package.json +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sevn Technologies
|
|
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/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# @sevn/elections-components
|
|
2
|
+
|
|
3
|
+
Componentes de apuração eleitoral em tempo real: mapas, rankings, progresso de
|
|
4
|
+
urnas, comparação entre turnos. São **JavaScript puro** — funcionam em qualquer
|
|
5
|
+
site, com ou sem framework, com ou sem build.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { Core, StateLeaders } from '@sevn/elections-components';
|
|
9
|
+
|
|
10
|
+
Core.applyKey('minha-chave-aqui');
|
|
11
|
+
|
|
12
|
+
const componente = new StateLeaders({
|
|
13
|
+
target: document.querySelector('#placeholder-state-leaders')
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Instalação
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @sevn/elections-components
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Não há dependência de peer para instalar. Svelte é detalhe de implementação
|
|
26
|
+
interno: ele vai embutido no pacote e não aparece na sua árvore de dependências.
|
|
27
|
+
|
|
28
|
+
## A chave
|
|
29
|
+
|
|
30
|
+
Todo componente precisa de uma chave antes de buscar dados. Ela identifica sua
|
|
31
|
+
CDN — é o subdomínio da API dedicada à sua conta:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
Core.applyKey('minha-chave-aqui');
|
|
35
|
+
// → https://minha-chave-aqui-api1.sevnelections.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Chame uma vez, no boot da aplicação, antes de montar qualquer componente. Sem
|
|
39
|
+
chave o componente monta normalmente e exibe a mensagem de configuração
|
|
40
|
+
faltando na própria área dele — nunca lança um erro que derrube sua página.
|
|
41
|
+
|
|
42
|
+
### Alta disponibilidade
|
|
43
|
+
|
|
44
|
+
Cada requisição tenta, em ordem:
|
|
45
|
+
|
|
46
|
+
1. `{chave}-api1.sevnelections.com` — 3 tentativas
|
|
47
|
+
2. `{chave}-api2.sevnelections.com` — 3 tentativas
|
|
48
|
+
3. `failover-api.sevntech.com` — servidor genérico de emergência
|
|
49
|
+
|
|
50
|
+
Depois de cair para um secundário, o cliente fica nele por 2 minutos antes de
|
|
51
|
+
retentar o primário, para não pagar os timeouts a cada requisição num dia de
|
|
52
|
+
pico. Respostas 4xx **não** promovem host: um 404 significa "recorte ainda não
|
|
53
|
+
publicado", e é a resposta correta, não uma falha.
|
|
54
|
+
|
|
55
|
+
O cache é compartilhado entre os hosts — trocar de servidor não invalida nada
|
|
56
|
+
do que já foi baixado.
|
|
57
|
+
|
|
58
|
+
## As três formas de usar
|
|
59
|
+
|
|
60
|
+
### 1. Import (Node, Bun, Deno, Vite, webpack, Next, Nuxt…)
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { Core, StateLeaders } from '@sevn/elections-components';
|
|
64
|
+
|
|
65
|
+
Core.applyKey('minha-chave-aqui');
|
|
66
|
+
|
|
67
|
+
const componente = new StateLeaders({
|
|
68
|
+
target: document.querySelector('#placeholder'),
|
|
69
|
+
year: 2026,
|
|
70
|
+
defaultOffice: 'governor'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Quando não precisar mais dele:
|
|
74
|
+
componente.destroy();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
O pacote é tree-shakeable: importar um componente não traz os outros 24.
|
|
78
|
+
|
|
79
|
+
### 2. Tag `<script>`, em HTML cru
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<div id="placeholder-state-leaders"></div>
|
|
83
|
+
|
|
84
|
+
<script src="https://cdn.sevnelections.com/components/v1/state-leaders.js?cid=minha-chave-aqui"></script>
|
|
85
|
+
<script>
|
|
86
|
+
const componente = new StateLeaders({
|
|
87
|
+
target: document.querySelector('#placeholder-state-leaders')
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A chave sai do `?cid=` da própria tag — não há o que configurar. Cada arquivo
|
|
93
|
+
expõe a classe com o nome do componente (`window.StateLeaders`) e também
|
|
94
|
+
`window.SevnElections.Core`, se você precisar ajustar algo.
|
|
95
|
+
|
|
96
|
+
Um arquivo por componente, ~34 KB gzip cada, tudo embutido.
|
|
97
|
+
|
|
98
|
+
### 3. CommonJS
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const { Core, StateLeaders } = require('@sevn/elections-components');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## API
|
|
105
|
+
|
|
106
|
+
### `new Componente(opções)`
|
|
107
|
+
|
|
108
|
+
`target` é obrigatório e aceita um elemento ou um seletor CSS. As demais chaves
|
|
109
|
+
são as props do componente.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
new StateLeaders({ target: '#ph', year: 2026, mock: true });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `componente.destroy()`
|
|
116
|
+
|
|
117
|
+
Desmonta e remove do DOM. Devolve uma `Promise`, caso você precise esperar a
|
|
118
|
+
saída terminar. Chamar duas vezes não faz nada.
|
|
119
|
+
|
|
120
|
+
### `Core`
|
|
121
|
+
|
|
122
|
+
| Método | Para quê |
|
|
123
|
+
| ------------------------------------------ | --------------------------------------------- |
|
|
124
|
+
| `Core.applyKey(chave)` | Configura a conta. Obrigatório. |
|
|
125
|
+
| `Core.setTheme({ primary, secondary, … })` | Sobrescreve as cores. |
|
|
126
|
+
| `Core.configure(opções)` | Ajusta hosts, CDN de assets, cache, timeouts. |
|
|
127
|
+
| `Core.getConfig()` | Lê a configuração atual. |
|
|
128
|
+
| `Core.isReady()` | Diz se já há hosts resolvidos. |
|
|
129
|
+
| `Core.activeHost()` | Host que respondeu por último. Diagnóstico. |
|
|
130
|
+
|
|
131
|
+
## Estilo
|
|
132
|
+
|
|
133
|
+
O CSS é injetado sozinho no momento em que o componente monta. Não há folha
|
|
134
|
+
para linkar.
|
|
135
|
+
|
|
136
|
+
Os componentes **herdam a tipografia da sua página** e trazem o próprio reset
|
|
137
|
+
escopado às raízes `sevn_*` — nada do que eles definem vaza para o resto do
|
|
138
|
+
seu site.
|
|
139
|
+
|
|
140
|
+
As cores são custom properties com prefixo `--sevn-`, então você pode
|
|
141
|
+
sobrescrevê-las pela cascata:
|
|
142
|
+
|
|
143
|
+
```css
|
|
144
|
+
:root {
|
|
145
|
+
--sevn-color-primary: #0a3e27;
|
|
146
|
+
--sevn-color-secondary: #c38214;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Ou pela API, que dá no mesmo:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
Core.setTheme({ primary: '#0a3e27', secondary: '#c38214' });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Se preferir carregar os tokens antes do JS, o arquivo também é publicado:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
import '@sevn/elections-components/styles.css';
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Props comuns
|
|
163
|
+
|
|
164
|
+
Todos os 25 componentes aceitam:
|
|
165
|
+
|
|
166
|
+
| Prop | Tipo | Padrão | Para quê |
|
|
167
|
+
| ---------- | ------------- | -------------- | ------------------------------------------------------------------------------------------------- |
|
|
168
|
+
| `mock` | `boolean` | `false` | Renderiza com dados de exemplo, sem tocar na API. Útil para montar o layout antes de ter a chave. |
|
|
169
|
+
| `rawStyle` | `boolean` | `false` | Remove o cartão externo (borda, fundo, respiro) para encaixar no seu layout. |
|
|
170
|
+
| `header` | `HeaderProps` | por componente | `{ title, description, tag, tagColor }` — o cabeçalho exibido no topo. |
|
|
171
|
+
|
|
172
|
+
## Catálogo
|
|
173
|
+
|
|
174
|
+
| Componente | Arquivo UMD | Título padrão | Props próprias |
|
|
175
|
+
| -------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
176
|
+
| `FinalResults` | `final-results.js` | Presidente | `showAd: boolean` = `true`<br>`data: any \| null`<br>`stateResults: StateResults \| null` |
|
|
177
|
+
| `StateWinners` | `state-winners.js` | Quem ganhou em cada estado | `data: any \| null` |
|
|
178
|
+
| `VoteCountingProgress` | `vote-counting-progress.js` | Evolução da apuração | `data: HeadToHeadLiveTimeline \| null` |
|
|
179
|
+
| `VoteComparisonByRound` | `vote-comparison-by-round.js` | Comparação de votos entre 1º e 2º turno | `defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
180
|
+
| `CountingProgress` | `counting-progress.js` | Progresso da Apuração | `year: number` = `2026`<br>`defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
181
|
+
| `StateLeaders` | `state-leaders.js` | Veja quem lidera em cada estado | `year: number` = `2026`<br>`defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
182
|
+
| `CurrentLeader` | `current-leader.js` | Liderança Atual | `data: any \| null` |
|
|
183
|
+
| `TopDispute` | `top-dispute.js` | Disputa no topo | `year: number` = `2026` |
|
|
184
|
+
| `CandidateRanking` | `candidate-ranking.js` | Posições de liderança | `year: number` = `2026` |
|
|
185
|
+
| `LiveSummary` | `live-summary.js` | Resumo em tempo real | `data: any \| null` |
|
|
186
|
+
| `CandidatePerformanceByMunicipality` | `candidate-performance-by-municipality.js` | Consulte o desempenho dos candidatos por município | `year: number` = `2026`<br>`defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
187
|
+
| `RoundResultsAnalysis` | `round-results-analysis.js` | Análise de resultados por turno | `year: number` = `2026`<br>`defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
188
|
+
| `ElectionStatus` | `election-status.js` | Situação eleitoral | `year: number` = `2026`<br>`defaultOffice: 'president' \| 'governor'` = `'president'` |
|
|
189
|
+
| `CandidateResultsByMunicipality` | `candidate-results-by-municipality.js` | Consulte o desempenho dos candidatos por município | `year: number` = `2026` |
|
|
190
|
+
| `MunicipalityPerfomanceMap` | `municipality-perfomance-map.js` | Consulte o desempenho dos candidatos por município | `data: any \| null`<br>`year: number` = `2026` |
|
|
191
|
+
| `CandidateByState` | `candidate-by-state.js` | Governador por Estado | `data: any \| null`<br>`year: number` = `2026` |
|
|
192
|
+
| `CandidateCarrouselStateResultsViewer` | `candidate-carrousel-state-results-viewer.js` | Governador | `year: number` = `2026`<br>`office: string` = `'governor'` |
|
|
193
|
+
| `CandidateDuelStateResultViewer` | `candidate-duel-state-result-viewer.js` | Governador | `data: any \| null`<br>`year: number` = `2026`<br>`showPosition: boolean` = `false`<br>`positionTagColor: string` |
|
|
194
|
+
| `CandidateMapByState` | `candidate-map-by-state.js` | Governador por Estado | `data: any \| null` |
|
|
195
|
+
| `ElectionRoundAnalysis` | `election-round-analysis.js` | Análise de resultados por turno | `year: number` = `2026` |
|
|
196
|
+
| `CandidateMultiRingChartPerformanceByMunicipality` | `candidate-multi-ring-chart-performance-by-municipality.js` | Consulte o desempenho dos candidatos por município | `year: number` = `2026` |
|
|
197
|
+
| `ResultsByOffice` | `results-by-office.js` | Resultados por cargo | `year: number` = `2026`<br>`defaultOffice: 'senator' \| 'federal_deputy' \| 'state_deputy'` = `'senator'`<br>`itemsPerPage: number` = `10` |
|
|
198
|
+
| `VotingMap` | `voting-map.js` | Mapa de Votação | `showAd: boolean` = `false`<br>`year: number` = `2026`<br>`round: number` = `1` |
|
|
199
|
+
| `CandidateCountingProgress` | `candidate-counting-progress.js` | Evolução da Apuração para Governador | `year: number` = `2026` |
|
|
200
|
+
| `PartyPerformance` | `party-performance.js` | Desempenho por Partido (senador/deputado) | `year: number` = `2026` |
|
|
201
|
+
|
|
202
|
+
## Limitações conhecidas
|
|
203
|
+
|
|
204
|
+
Os tamanhos internos usam `rem`, que resolve contra o `font-size` do `<html>`
|
|
205
|
+
da sua página. Se você usa a técnica de `html { font-size: 62.5% }`, os
|
|
206
|
+
componentes vão encolher junto. Enquanto isso não muda, contorne isolando o
|
|
207
|
+
container:
|
|
208
|
+
|
|
209
|
+
```css
|
|
210
|
+
#placeholder {
|
|
211
|
+
font-size: 16px;
|
|
212
|
+
}
|
|
213
|
+
html {
|
|
214
|
+
font-size: 100%;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Desenvolvimento
|
|
219
|
+
|
|
220
|
+
Este repositório contém tanto a biblioteca quanto o showcase que a demonstra.
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
bun install
|
|
224
|
+
bun run dev # showcase em http://localhost:4321
|
|
225
|
+
bun run check # svelte-check
|
|
226
|
+
bun run test # testes unitários (transporte, config)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Empacotamento:
|
|
230
|
+
|
|
231
|
+
```sh
|
|
232
|
+
bun run build:pkg # ESM + CJS + tipos + estilos + 25 bundles UMD
|
|
233
|
+
bun run test:types # type-check de um consumidor contra o dist publicado
|
|
234
|
+
bun run serve:test # sobe test/umd.html para exercitar o UMD no navegador
|
|
235
|
+
bun run build:cdn # monta dist-cdn/ na estrutura do R2
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Os componentes são escritos em Svelte 5 (`src/components`), sobre primitivos
|
|
239
|
+
internos (`src/lib`). Nada de `src/lib` é público: mudanças ali não são
|
|
240
|
+
breaking changes. O que o consumidor vê é `src/index.ts` — classes JS geradas
|
|
241
|
+
por `defineComponent`, com as props tipadas em `src/components/props.ts`.
|
|
242
|
+
|
|
243
|
+
O showcase é SvelteKit, mas **o pacote não é**: `vite.lib.config.ts` e
|
|
244
|
+
`scripts/build-umd.mjs` não usam o plugin do SvelteKit. O `@sveltejs/package`
|
|
245
|
+
não é usado em lugar nenhum — ele não bundla, só copia `.svelte`, e era
|
|
246
|
+
exatamente isso que obrigava o consumidor a ter Svelte.
|
|
247
|
+
|
|
248
|
+
### Publicação
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
npm publish # prepublishOnly roda build, testes e publint
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Subindo os arquivos para o R2
|
|
255
|
+
|
|
256
|
+
São ~5.600 arquivos, e o **dashboard da Cloudflare aceita no máximo 100 por
|
|
257
|
+
upload** — então esta parte é obrigatoriamente pela CLI.
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
wrangler login # uma vez, interativo
|
|
261
|
+
bun run build:cdn # gera dist-cdn/
|
|
262
|
+
node scripts/upload-cdn.mjs --bucket=SEU_BUCKET --dry-run # confere antes
|
|
263
|
+
node scripts/upload-cdn.mjs --bucket=SEU_BUCKET
|
|
264
|
+
node scripts/upload-cdn.mjs --bucket=SEU_BUCKET --cors # uma vez só
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
O `wrangler r2 object put` envia um objeto por vez, então o script paraleliza
|
|
268
|
+
(12 por padrão, `--concurrency=N` para mudar) e grava um manifesto em
|
|
269
|
+
`dist-cdn/.uploaded.json`. Se cair no meio, rodar o mesmo comando retoma de
|
|
270
|
+
onde parou em vez de recomeçar os 5.600. `--force` ignora o manifesto,
|
|
271
|
+
`--only=components` sobe apenas os bundles UMD — que é o caso quando você
|
|
272
|
+
republica o pacote sem ter mexido nos mapas.
|
|
273
|
+
|
|
274
|
+
Cada objeto sobe com `cache-control: public, max-age=31536000, immutable`,
|
|
275
|
+
porque os caminhos são versionados: o conteúdo de uma chave `/v1/` nunca muda,
|
|
276
|
+
mudar significa publicar `/v2/`.
|
|
277
|
+
|
|
278
|
+
O `--cors` aplica o `r2-cors.json` do repositório. Sem isso o `fetch` dos mapas
|
|
279
|
+
falha a partir do domínio do cliente — é requisição cross-origin.
|
|
280
|
+
|
|
281
|
+
Depois de subir, falta apontar o domínio (`wrangler r2 bucket domain add`).
|
|
282
|
+
|
|
283
|
+
**Alternativa mais rápida**, se você for repetir isso com frequência: o R2 tem
|
|
284
|
+
API compatível com S3, então o `rclone` funciona e faz upload incremental de
|
|
285
|
+
verdade (só o que mudou), com muito mais paralelismo — cada `wrangler r2 object
|
|
286
|
+
put` paga o custo de subir um processo Node.
|
|
287
|
+
|
|
288
|
+
```sh
|
|
289
|
+
rclone sync dist-cdn/ r2:SEU_BUCKET/ --transfers=32 --checksum
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Requer configurar um remote `r2` com um token de API do R2 (`rclone config`,
|
|
293
|
+
tipo `s3`, provider `Cloudflare`).
|
|
294
|
+
|
|
295
|
+
## Licença
|
|
296
|
+
|
|
297
|
+
MIT
|