ilibras-widget 1.4.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/CHANGELOG.md +335 -0
- package/LICENSE +21 -0
- package/README.md +713 -0
- package/dist/ilibras-widget.cjs +1415 -0
- package/dist/ilibras-widget.js +1396 -0
- package/dist/ilibras-widget.mjs +1394 -0
- package/index.d.ts +118 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
# 🎯 iLibras Widget - Widget de Atendimento Embarcável
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
|
5
|
+
[](https://developer.mozilla.org/en-US/docs/Web/CSS)
|
|
6
|
+
|
|
7
|
+
Widget embarcável similar ao WhatsApp para captura de dados (Nome e CPF) com integração via API. Envia dados de forma segura via POST e redireciona para plataforma de atendimento. Ideal para integração em sites, sistemas e plataformas web.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## ✨ Características
|
|
12
|
+
|
|
13
|
+
- 🚀 **Fácil integração** - Apenas 2 linhas de código
|
|
14
|
+
- 🎨 **Totalmente personalizável** - Cores, posição, textos e mais
|
|
15
|
+
- 📱 **Responsivo** - Funciona perfeitamente em desktop e mobile
|
|
16
|
+
- ✅ **Validação automática** - CPF validado antes do envio
|
|
17
|
+
- 🔒 **Envio seguro** - Dados enviados via POST para API
|
|
18
|
+
- 🎭 **Máscaras de entrada** - Formatação automática de CPF
|
|
19
|
+
- 🌐 **Universal** - Funciona em qualquer site ou sistema
|
|
20
|
+
- ♿ **Acessível** - Suporte a leitores de tela e navegação por teclado
|
|
21
|
+
- 🌙 **Tema escuro** - Adapta-se automaticamente às preferências do usuário
|
|
22
|
+
|
|
23
|
+
## 📦 Instalação
|
|
24
|
+
|
|
25
|
+
O widget é um arquivo só: estilo e logo vão embutidos. Não há CSS nem imagem
|
|
26
|
+
para baixar junto, e não há caminho para configurar errado.
|
|
27
|
+
|
|
28
|
+
> ⚠️ Em qualquer uma das opções, troque `SEU_TOKEN_AQUI` pelo token que a
|
|
29
|
+
> equipe iLibras forneceu para o contrato.
|
|
30
|
+
|
|
31
|
+
### Opção 1: npm
|
|
32
|
+
|
|
33
|
+
Para projetos com bundler — React, Vue, Svelte, Next, Nuxt, Astro, Vite,
|
|
34
|
+
webpack:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm i ilibras-widget
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import ILibrasWidget from 'ilibras-widget';
|
|
42
|
+
|
|
43
|
+
const widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Importar não cria widget nenhum — quem decide quando instanciar é você. Isso é
|
|
47
|
+
o que permite usar o pacote em projeto com renderização no servidor, onde o
|
|
48
|
+
import acontece onde não existe `document`.
|
|
49
|
+
|
|
50
|
+
O pacote traz tipos TypeScript; não é preciso instalar `@types`.
|
|
51
|
+
|
|
52
|
+
#### React
|
|
53
|
+
|
|
54
|
+
```jsx
|
|
55
|
+
import { useEffect } from 'react';
|
|
56
|
+
import ILibrasWidget from 'ilibras-widget';
|
|
57
|
+
|
|
58
|
+
export function Acessibilidade() {
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' });
|
|
61
|
+
// Sem isto, cada montagem deixa um widget para trás e o site acumula
|
|
62
|
+
// botões flutuantes.
|
|
63
|
+
return () => widget.destroy();
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Vue
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<script setup>
|
|
74
|
+
import { onMounted, onBeforeUnmount } from 'vue';
|
|
75
|
+
import ILibrasWidget from 'ilibras-widget';
|
|
76
|
+
|
|
77
|
+
let widget;
|
|
78
|
+
onMounted(() => { widget = new ILibrasWidget({ token: 'SEU_TOKEN_AQUI' }); });
|
|
79
|
+
onBeforeUnmount(() => widget?.destroy());
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Opção 2: Via CDN
|
|
84
|
+
|
|
85
|
+
Sem build, sem instalar nada. A versão fica presa na URL — atualizar é trocar
|
|
86
|
+
o número:
|
|
87
|
+
|
|
88
|
+
```html
|
|
89
|
+
<script src="https://cdn.jsdelivr.net/npm/ilibras-widget@1/dist/ilibras-widget.js"></script>
|
|
90
|
+
<script>
|
|
91
|
+
new ILibrasWidget({
|
|
92
|
+
token: 'SEU_TOKEN_AQUI'
|
|
93
|
+
});
|
|
94
|
+
</script>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use `@1` para receber correções automaticamente, ou fixe a versão exata
|
|
98
|
+
(`@1.4.0`) se preferir controlar cada atualização.
|
|
99
|
+
|
|
100
|
+
### Opção 3: Arquivo baixado
|
|
101
|
+
|
|
102
|
+
Para WordPress, HTML solto e qualquer site sem build. Baixe
|
|
103
|
+
[`ilibras-widget.js`](ilibras-widget.js) e hospede junto do seu site:
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<script src="ilibras-widget.js"></script>
|
|
107
|
+
<script>
|
|
108
|
+
new ILibrasWidget({
|
|
109
|
+
token: 'SEU_TOKEN_AQUI'
|
|
110
|
+
});
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
> Instalações antigas incluíam também um `<link rel="stylesheet">` para
|
|
115
|
+
> `ilibras-widget.css`. A partir da v1.4.0 o estilo vai dentro do JavaScript e
|
|
116
|
+
> essa linha virou desnecessária — mas deixá-la não quebra nada.
|
|
117
|
+
|
|
118
|
+
### Instalação sem escrever JavaScript
|
|
119
|
+
|
|
120
|
+
Em qualquer uma das opções com `<script>`, dá para configurar o widget antes
|
|
121
|
+
de carregá-lo, e ele se cria sozinho:
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<script>
|
|
125
|
+
window.iLibrasWidgetConfig = { token: 'SEU_TOKEN_AQUI' };
|
|
126
|
+
</script>
|
|
127
|
+
<script src="https://cdn.jsdelivr.net/npm/ilibras-widget@1/dist/ilibras-widget.js"></script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 🚀 Uso Rápido
|
|
131
|
+
|
|
132
|
+
### Configuração Básica
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
new ILibrasWidget({
|
|
136
|
+
token: 'SEU_TOKEN_AQUI'
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> ⚠️ O parâmetro `token` é **obrigatório**. Entre em contato com a equipe iLibras para obter seu token.
|
|
141
|
+
|
|
142
|
+
### Configuração Personalizada
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
new ILibrasWidget({
|
|
146
|
+
position: 'bottom-right',
|
|
147
|
+
redirectUrl: 'https://teste.com.br',
|
|
148
|
+
primaryColor: '#4A90E2',
|
|
149
|
+
title: 'iLibras',
|
|
150
|
+
message: 'Olá! Como podemos ajudar?',
|
|
151
|
+
buttonText: 'Iniciar atendimento',
|
|
152
|
+
agendamento: true,
|
|
153
|
+
scheduleButtonText: 'Agendar uma conversa',
|
|
154
|
+
token: 'SEU_TOKEN_AQUI',
|
|
155
|
+
zIndex: 9999
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## ⚙️ Opções de Configuração
|
|
160
|
+
|
|
161
|
+
| Opção | Tipo | Padrão | Descrição |
|
|
162
|
+
|-------|------|--------|-----------|
|
|
163
|
+
| `position` | `string` | `'bottom-right'` | Posição do widget: `'bottom-right'`, `'bottom-left'`, `'top-right'`, `'top-left'` |
|
|
164
|
+
| `redirectUrl` | `string` | `'https://teste.com.br'` | URL para redirecionamento após envio |
|
|
165
|
+
| `primaryColor` | `string` | `'#4A90E2'` | Cor principal do widget (hex) |
|
|
166
|
+
| `title` | `string` | `'iLibras'` | Título exibido no cabeçalho |
|
|
167
|
+
| `message` | `string` | `'Olá, somos a equipe...'` | Mensagem de boas-vindas |
|
|
168
|
+
| `buttonText` | `string` | `'Iniciar atendimento em Libras'` | Texto do botão de envio |
|
|
169
|
+
| `agendamento` | `boolean` | `true` | Mostra o segundo botão, que leva direto à escolha de data e horário em vez da fila ao vivo. Deixe `false` em site que só atende ao vivo |
|
|
170
|
+
| `scheduleButtonText` | `string` | `'Agendar uma conversa'` | Texto do botão de agendamento |
|
|
171
|
+
| `token` | `string` | `''` | **OBRIGATÓRIO** — token de autenticação da API |
|
|
172
|
+
| `zIndex` | `number` | `9999` | Z-index do widget |
|
|
173
|
+
| `apiUrl` | `string` | endpoint de produção | Endpoint de cadastro. Troque apenas para apontar a um ambiente de homologação |
|
|
174
|
+
| `timeoutMs` | `number` | `15000` | Prazo de cada tentativa. Passado isso a requisição é abortada e a pessoa recebe uma mensagem, em vez de ficar no "Enviando..." |
|
|
175
|
+
| `tentativas` | `number` | `2` | Quantas vezes tentar quando a falha é de rede ou do servidor. Erro de validação e limite de uso nunca são repetidos |
|
|
176
|
+
| `fallback` | `object` | `{}` | Canais alternativos exibidos quando o serviço não responde: `{ telefone, email, url, texto }` |
|
|
177
|
+
|
|
178
|
+
### Plano de contingência (`fallback`)
|
|
179
|
+
|
|
180
|
+
Se a API do iLibras não responder, o widget mostra a falha dentro do próprio
|
|
181
|
+
modal e — se você tiver configurado — oferece outro caminho de atendimento, em
|
|
182
|
+
vez de deixar a pessoa sem saída:
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
new ILibrasWidget({
|
|
186
|
+
token: 'seu_token_aqui',
|
|
187
|
+
fallback: {
|
|
188
|
+
telefone: '(11) 4002-8922',
|
|
189
|
+
email: 'acessibilidade@suaempresa.com.br',
|
|
190
|
+
url: 'https://suaempresa.com.br/atendimento',
|
|
191
|
+
texto: 'Enquanto isso, você pode falar com a gente por aqui:'
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Qualquer combinação dos quatro campos funciona; o bloco só aparece se pelo
|
|
197
|
+
menos um canal estiver preenchido.
|
|
198
|
+
|
|
199
|
+
## 📊 Integração com API
|
|
200
|
+
|
|
201
|
+
Quando o usuário submete o formulário, os dados são enviados via **POST** (método seguro) para a API configurada:
|
|
202
|
+
|
|
203
|
+
### Endpoint da API
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
POST https://sistema.ilibras.com.br/administrativo/api/clientes/cadastrar.php
|
|
207
|
+
Content-Type: multipart/form-data
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Dados Enviados
|
|
211
|
+
|
|
212
|
+
Os dados são enviados como **FormData** (similar a um formulário HTML tradicional):
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
nome: "João Silva"
|
|
216
|
+
cpf: "12345678900"
|
|
217
|
+
telefone: "11987654321"
|
|
218
|
+
token: "seu_token_de_autenticacao"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Campo | Descrição | Formato |
|
|
222
|
+
|-------|-----------|------|
|
|
223
|
+
| `nome` | Nome completo do usuário | String |
|
|
224
|
+
| `cpf` | CPF sem formatação (apenas números) | String numérica (11 dígitos) |
|
|
225
|
+
| `telefone` | Telefone sem formatação (apenas números) | String numérica (10-11 dígitos) |
|
|
226
|
+
| `token` | Token de autenticação fornecido pela equipe | String |
|
|
227
|
+
|
|
228
|
+
### Resposta Esperada da API
|
|
229
|
+
|
|
230
|
+
A API deve retornar um JSON com um link de redirecionamento:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"status": "OK",
|
|
235
|
+
"link_fila": "https://sistema.ilibras.com.br/acesso.php?token=eyJp...",
|
|
236
|
+
"codigo": "4c6779a0aadb331ff53047879447809812fc9d5778254525d66466339135da5b",
|
|
237
|
+
"id_atendimento": "717",
|
|
238
|
+
"id_surdo": 417
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Campos aceitos para redirecionamento (em ordem de prioridade):
|
|
243
|
+
- `link_fila` ⭐ **Recomendado**
|
|
244
|
+
- `link`
|
|
245
|
+
- `url`
|
|
246
|
+
- `redirect`
|
|
247
|
+
|
|
248
|
+
**Comportamento após envio:**
|
|
249
|
+
1. ✅ O widget valida e envia os dados
|
|
250
|
+
2. ✅ A API processa e retorna o link de atendimento
|
|
251
|
+
3. ✅ O link abre em **nova aba** do navegador
|
|
252
|
+
4. ✅ O modal do widget fecha automaticamente
|
|
253
|
+
5. ✅ Usuário permanece na página original enquanto o atendimento abre em outra aba
|
|
254
|
+
|
|
255
|
+
## 🎯 Exemplos de Uso
|
|
256
|
+
|
|
257
|
+
### Exemplo 1: Widget Simples
|
|
258
|
+
|
|
259
|
+
```html
|
|
260
|
+
<!DOCTYPE html>
|
|
261
|
+
<html>
|
|
262
|
+
<head>
|
|
263
|
+
<title>Meu Site</title>
|
|
264
|
+
</head>
|
|
265
|
+
<body>
|
|
266
|
+
<h1>Bem-vindo!</h1>
|
|
267
|
+
|
|
268
|
+
<!-- Widget -->
|
|
269
|
+
<link rel="stylesheet" href="ilibras-widget.css">
|
|
270
|
+
<script src="ilibras-widget.js"></script>
|
|
271
|
+
<script>
|
|
272
|
+
new ILibrasWidget();
|
|
273
|
+
</script>
|
|
274
|
+
</body>
|
|
275
|
+
</html>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Exemplo 2: Posição Personalizada
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// Widget no canto inferior esquerdo
|
|
282
|
+
new ILibrasWidget({
|
|
283
|
+
position: 'bottom-left'
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Exemplo 3: Customização Completa
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
new ILibrasWidget({
|
|
291
|
+
position: 'bottom-right',
|
|
292
|
+
redirectUrl: 'https://minhaplataforma.com.br/atendimento',
|
|
293
|
+
primaryColor: '#0066CC',
|
|
294
|
+
title: 'Suporte Premium',
|
|
295
|
+
message: 'Nossa equipe está pronta para atender você! Preencha os dados abaixo.',
|
|
296
|
+
buttonText: 'Solicitar Atendimento',
|
|
297
|
+
zIndex: 10000
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Exemplo 4: Auto-inicialização Global
|
|
302
|
+
|
|
303
|
+
```html
|
|
304
|
+
<!-- Defina a configuração antes de carregar o script -->
|
|
305
|
+
<script>
|
|
306
|
+
window.iLibrasWidgetConfig = {
|
|
307
|
+
position: 'bottom-right',
|
|
308
|
+
redirectUrl: 'https://teste.com.br',
|
|
309
|
+
primaryColor: '#FF5722'
|
|
310
|
+
};
|
|
311
|
+
</script>
|
|
312
|
+
<script src="ilibras-widget.js"></script>
|
|
313
|
+
<!-- Widget será inicializado automaticamente -->
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Exemplo 5: Controle Programático
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
// Guarde a instância
|
|
320
|
+
const widget = new ILibrasWidget();
|
|
321
|
+
|
|
322
|
+
// Abrir widget programaticamente
|
|
323
|
+
widget.openWidget();
|
|
324
|
+
|
|
325
|
+
// Fechar widget
|
|
326
|
+
widget.closeWidget();
|
|
327
|
+
|
|
328
|
+
// Alternar (abrir/fechar)
|
|
329
|
+
widget.toggleWidget();
|
|
330
|
+
|
|
331
|
+
// Remover widget da página
|
|
332
|
+
widget.destroy();
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 🎨 Personalização Avançada
|
|
336
|
+
|
|
337
|
+
### Alterar Estilos via CSS
|
|
338
|
+
|
|
339
|
+
Você pode sobrescrever os estilos padrão:
|
|
340
|
+
|
|
341
|
+
```css
|
|
342
|
+
/* Customizar botão flutuante */
|
|
343
|
+
.ilibras-widget-button {
|
|
344
|
+
width: 70px !important;
|
|
345
|
+
height: 70px !important;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* Customizar modal */
|
|
349
|
+
.ilibras-widget-modal {
|
|
350
|
+
border-radius: 20px !important;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* Customizar inputs */
|
|
354
|
+
.ilibras-widget-form-group input {
|
|
355
|
+
border-radius: 12px !important;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## 🔧 API JavaScript
|
|
360
|
+
|
|
361
|
+
### Métodos Públicos
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
const widget = new ILibrasWidget(config);
|
|
365
|
+
|
|
366
|
+
// Abrir o widget
|
|
367
|
+
widget.openWidget();
|
|
368
|
+
|
|
369
|
+
// Fechar o widget
|
|
370
|
+
widget.closeWidget();
|
|
371
|
+
|
|
372
|
+
// Alternar estado (abrir/fechar)
|
|
373
|
+
widget.toggleWidget();
|
|
374
|
+
|
|
375
|
+
// Destruir o widget e remover do DOM
|
|
376
|
+
widget.destroy();
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Propriedades
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
// Verificar se o widget está aberto
|
|
383
|
+
console.log(widget.isOpen); // true ou false
|
|
384
|
+
|
|
385
|
+
// Acessar configuração
|
|
386
|
+
console.log(widget.config);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## 🌐 Compatibilidade
|
|
390
|
+
|
|
391
|
+
### Navegadores Suportados
|
|
392
|
+
|
|
393
|
+
- ✅ Chrome 90+
|
|
394
|
+
- ✅ Firefox 88+
|
|
395
|
+
- ✅ Safari 14+
|
|
396
|
+
- ✅ Edge 90+
|
|
397
|
+
- ✅ Opera 76+
|
|
398
|
+
- ✅ Mobile Browsers (iOS Safari, Chrome Mobile)
|
|
399
|
+
|
|
400
|
+
### Frameworks e CMS
|
|
401
|
+
|
|
402
|
+
O widget funciona com:
|
|
403
|
+
|
|
404
|
+
- ✅ HTML estático
|
|
405
|
+
- ✅ WordPress
|
|
406
|
+
- ✅ Joomla
|
|
407
|
+
- ✅ Drupal
|
|
408
|
+
- ✅ React
|
|
409
|
+
- ✅ Vue.js
|
|
410
|
+
- ✅ Angular
|
|
411
|
+
- ✅ Next.js
|
|
412
|
+
- ✅ Qualquer framework JavaScript
|
|
413
|
+
|
|
414
|
+
## 📱 Responsividade
|
|
415
|
+
|
|
416
|
+
O widget se adapta automaticamente a diferentes tamanhos de tela:
|
|
417
|
+
|
|
418
|
+
- Desktop: Modal de 380px de largura
|
|
419
|
+
- Tablet: Modal responsivo com margens
|
|
420
|
+
- Mobile: Modal em tela cheia com otimizações
|
|
421
|
+
|
|
422
|
+
## 🔒 Segurança e Validação
|
|
423
|
+
|
|
424
|
+
### Envio Seguro via POST
|
|
425
|
+
|
|
426
|
+
Todos os dados são enviados via **método POST** (não via URL/GET), garantindo:
|
|
427
|
+
- Dados não ficam expostos na URL
|
|
428
|
+
- Maior segurança no tráfego de informações sensíveis
|
|
429
|
+
- Conformidade com boas práticas de desenvolvimento web
|
|
430
|
+
|
|
431
|
+
### Validação de CPF
|
|
432
|
+
|
|
433
|
+
O widget valida o CPF antes do envio usando o algoritmo de verificação de dígitos. CPFs inválidos são rejeitados automaticamente.
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
// CPFs aceitos: números válidos com 11 dígitos
|
|
437
|
+
// CPFs rejeitados:
|
|
438
|
+
// - Números com menos de 11 dígitos
|
|
439
|
+
// - Sequências repetidas (111.111.111-11)
|
|
440
|
+
// - Dígitos verificadores incorretos
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Validação de Nome
|
|
444
|
+
|
|
445
|
+
- Entre 3 e 120 caracteres
|
|
446
|
+
- Apenas letras, espaços, apóstrofos, hífens e pontos — dígito, emoji e
|
|
447
|
+
pontuação de código são rejeitados
|
|
448
|
+
- Exige nome e sobrenome
|
|
449
|
+
|
|
450
|
+
### Validação de Telefone
|
|
451
|
+
|
|
452
|
+
- DDD conferido contra a lista de códigos em uso no país
|
|
453
|
+
- Celular com 9 dígitos precisa começar em 9; fixo com 8 dígitos, entre 2 e 5
|
|
454
|
+
- Sequências repetidas (00000000000) são rejeitadas
|
|
455
|
+
|
|
456
|
+
### Sanitização de Dados
|
|
457
|
+
|
|
458
|
+
- CPF e telefone: apenas dígitos
|
|
459
|
+
- Nome: espaços das pontas removidos e sequências de espaço reduzidas a um
|
|
460
|
+
|
|
461
|
+
> **A validação do widget é conveniência, não barreira.** Ela roda no navegador
|
|
462
|
+
> e qualquer pessoa a desliga pelo console. As mesmas regras valem no servidor,
|
|
463
|
+
> e é lá que elas decidem — o widget as repete só para avisar cedo e em
|
|
464
|
+
> português, sem uma ida à rede.
|
|
465
|
+
|
|
466
|
+
## 🤖 Proteção contra automação
|
|
467
|
+
|
|
468
|
+
O widget participa de duas checagens simples do lado do servidor:
|
|
469
|
+
|
|
470
|
+
- **campo-armadilha** (`site_url`): existe no formulário, fica fora da tela e
|
|
471
|
+
fora da leitura de tela. Só chega preenchido quando quem respondeu foi um
|
|
472
|
+
programa que preenche todo campo que encontra;
|
|
473
|
+
- **tempo de preenchimento** (`iniciado_em`): o instante em que o modal foi
|
|
474
|
+
aberto. Envio em menos de 3 segundos é recusado.
|
|
475
|
+
|
|
476
|
+
Nenhuma das duas é intransponível, e não é o que se espera delas: quem segura
|
|
477
|
+
volume é o limite de requisições por IP e por contrato, aplicado no servidor.
|
|
478
|
+
Elas existem para que o ataque trivial — um `curl` em laço — não chegue até lá.
|
|
479
|
+
|
|
480
|
+
**Não há CAPTCHA, e isso é deliberado:** o público deste widget é surdo, e
|
|
481
|
+
desafio visual ou de áudio é exatamente a barreira que o produto existe para
|
|
482
|
+
remover.
|
|
483
|
+
|
|
484
|
+
Se o limite for atingido, a API responde `429` e o widget mostra a mensagem do
|
|
485
|
+
servidor (que já diz quanto esperar) sem repetir a requisição.
|
|
486
|
+
|
|
487
|
+
## ♿ Acessibilidade
|
|
488
|
+
|
|
489
|
+
O widget segue as WCAG 2.1 nível AA nos pontos que dependem dele:
|
|
490
|
+
|
|
491
|
+
- **teclado**: o gatilho é um `<button>` de verdade; o modal é um
|
|
492
|
+
`role="dialog"` com `aria-modal`, foco levado ao primeiro campo ao abrir,
|
|
493
|
+
`Tab` preso dentro da janela, `Esc` para fechar e foco devolvido a quem
|
|
494
|
+
abriu;
|
|
495
|
+
- **leitor de tela**: todo campo tem rótulo associado, `aria-required` e
|
|
496
|
+
`aria-describedby` ligando dica e mensagem de erro; o estado do envio é
|
|
497
|
+
anunciado por uma região `aria-live`; falhas de serviço usam `role="alert"`;
|
|
498
|
+
- **erros**: aparecem junto do campo que os causou, com ícone além da cor, e o
|
|
499
|
+
foco vai para o primeiro campo inválido — nada de `alert()`;
|
|
500
|
+
- **contraste**: textos e mensagens em AA (4.5:1); campo inválido sinalizado por
|
|
501
|
+
borda mais espessa e símbolo, não só por cor;
|
|
502
|
+
- **preferências do sistema**: `prefers-reduced-motion` desliga as animações e
|
|
503
|
+
`forced-colors` (alto contraste do Windows) mantém contornos visíveis;
|
|
504
|
+
- **idioma**: o container declara `lang="pt-BR"`, para o leitor de tela usar a
|
|
505
|
+
voz certa mesmo num site em outro idioma.
|
|
506
|
+
|
|
507
|
+
## 🎓 Exemplos Práticos
|
|
508
|
+
|
|
509
|
+
### WordPress
|
|
510
|
+
|
|
511
|
+
```php
|
|
512
|
+
<?php
|
|
513
|
+
// Adicione ao footer.php ou use wp_footer hook
|
|
514
|
+
function adicionar_ilibras_widget() {
|
|
515
|
+
?>
|
|
516
|
+
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/ilibras-widget.css">
|
|
517
|
+
<script src="<?php echo get_template_directory_uri(); ?>/ilibras-widget.js"></script>
|
|
518
|
+
<script>
|
|
519
|
+
new ILibrasWidget({
|
|
520
|
+
redirectUrl: 'https://meusite.com.br/atendimento'
|
|
521
|
+
});
|
|
522
|
+
</script>
|
|
523
|
+
<?php
|
|
524
|
+
}
|
|
525
|
+
add_action('wp_footer', 'adicionar_ilibras_widget');
|
|
526
|
+
?>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### React
|
|
530
|
+
|
|
531
|
+
```jsx
|
|
532
|
+
import { useEffect } from 'react';
|
|
533
|
+
|
|
534
|
+
function App() {
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
// Carregar CSS
|
|
537
|
+
const link = document.createElement('link');
|
|
538
|
+
link.rel = 'stylesheet';
|
|
539
|
+
link.href = '/ilibras-widget.css';
|
|
540
|
+
document.head.appendChild(link);
|
|
541
|
+
|
|
542
|
+
// Carregar JS
|
|
543
|
+
const script = document.createElement('script');
|
|
544
|
+
script.src = '/ilibras-widget.js';
|
|
545
|
+
script.onload = () => {
|
|
546
|
+
new window.ILibrasWidget({
|
|
547
|
+
position: 'bottom-right'
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
document.body.appendChild(script);
|
|
551
|
+
|
|
552
|
+
return () => {
|
|
553
|
+
// Cleanup
|
|
554
|
+
if (window.iLibrasWidgetInstance) {
|
|
555
|
+
window.iLibrasWidgetInstance.destroy();
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}, []);
|
|
559
|
+
|
|
560
|
+
return <div className="App">Meu App</div>;
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Vue.js
|
|
565
|
+
|
|
566
|
+
```vue
|
|
567
|
+
<template>
|
|
568
|
+
<div id="app">
|
|
569
|
+
<h1>Meu App Vue</h1>
|
|
570
|
+
</div>
|
|
571
|
+
</template>
|
|
572
|
+
|
|
573
|
+
<script>
|
|
574
|
+
export default {
|
|
575
|
+
name: 'App',
|
|
576
|
+
mounted() {
|
|
577
|
+
// Carregar widget
|
|
578
|
+
const link = document.createElement('link');
|
|
579
|
+
link.rel = 'stylesheet';
|
|
580
|
+
link.href = '/ilibras-widget.css';
|
|
581
|
+
document.head.appendChild(link);
|
|
582
|
+
|
|
583
|
+
const script = document.createElement('script');
|
|
584
|
+
script.src = '/ilibras-widget.js';
|
|
585
|
+
script.onload = () => {
|
|
586
|
+
new window.ILibrasWidget({
|
|
587
|
+
position: 'bottom-right',
|
|
588
|
+
redirectUrl: 'https://teste.com.br'
|
|
589
|
+
});
|
|
590
|
+
};
|
|
591
|
+
document.body.appendChild(script);
|
|
592
|
+
},
|
|
593
|
+
beforeUnmount() {
|
|
594
|
+
if (window.iLibrasWidgetInstance) {
|
|
595
|
+
window.iLibrasWidgetInstance.destroy();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
</script>
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## 📄 Estrutura de Arquivos
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
Ilibras_widget/
|
|
606
|
+
├── src/ # A fonte — é aqui que se mexe
|
|
607
|
+
│ ├── ILibrasWidget.js # a classe
|
|
608
|
+
│ ├── index.js # entrada do pacote npm
|
|
609
|
+
│ ├── navegador.js # entrada da build de <script>
|
|
610
|
+
│ └── assets-gerados.js # GERADO: css e logo embutidos
|
|
611
|
+
├── scripts/
|
|
612
|
+
│ ├── gerar-assets.mjs # css + svg -> src/assets-gerados.js
|
|
613
|
+
│ └── build.mjs # src/ -> dist/ e raiz
|
|
614
|
+
├── tests/ # npm test
|
|
615
|
+
├── ilibras-widget.css # Estilo (fonte do embutido)
|
|
616
|
+
├── ilibras-LOGO.svg # Logo (fonte do embutido)
|
|
617
|
+
├── ilibras-widget.js # GERADO: build de <script>, no endereço de sempre
|
|
618
|
+
├── index.d.ts # Tipos TypeScript
|
|
619
|
+
├── exemplo.html # Página de exemplo e demonstração
|
|
620
|
+
└── README.md # Esta documentação
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## 🛠️ Desenvolvimento
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
npm install # só o esbuild
|
|
627
|
+
npm run build # gera dist/ e o ilibras-widget.js da raiz
|
|
628
|
+
npm test # garante que estilo e logo saíram embutidos nas três builds
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
> ⚠️ **Não edite `ilibras-widget.js` na raiz nem `src/assets-gerados.js`.** Os
|
|
632
|
+
> dois são gerados e sobrescritos pelo build. A lógica vive em
|
|
633
|
+
> `src/ILibrasWidget.js`; o visual, em `ilibras-widget.css`.
|
|
634
|
+
|
|
635
|
+
O `ilibras-widget.js` da raiz é versionado de propósito, mesmo sendo gerado:
|
|
636
|
+
sites já instalados apontam para esse caminho e o README manda baixá-lo de lá.
|
|
637
|
+
Mudar o endereço quebraria justamente quem não usa npm.
|
|
638
|
+
|
|
639
|
+
### Publicando no npm
|
|
640
|
+
|
|
641
|
+
`prepublishOnly` roda o build sozinho, então basta:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
npm version minor # ou patch / major
|
|
645
|
+
npm publish
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Confira o conteúdo antes com `npm pack --dry-run`.
|
|
649
|
+
|
|
650
|
+
## 🐛 Solução de Problemas
|
|
651
|
+
|
|
652
|
+
### Widget não aparece
|
|
653
|
+
|
|
654
|
+
1. Confira se o script carregou (aba Network do navegador)
|
|
655
|
+
2. Veja o console: sem `token`, o widget avisa ao tentar enviar
|
|
656
|
+
3. Usando bundler? Lembre que `import` não cria o widget — é preciso
|
|
657
|
+
`new ILibrasWidget({ token })`
|
|
658
|
+
|
|
659
|
+
### Estilos não aplicados
|
|
660
|
+
|
|
661
|
+
A partir da v1.4.0 o estilo vai dentro do JavaScript; não há mais CSS para
|
|
662
|
+
carregar na ordem certa. Se o visual está estranho, é conflito com o CSS do
|
|
663
|
+
site — usar `!important` nas suas regras resolve.
|
|
664
|
+
|
|
665
|
+
### Aparecem dois widgets
|
|
666
|
+
|
|
667
|
+
Em React, Vue ou Angular, chame `destroy()` na desmontagem do componente. Sem
|
|
668
|
+
isso cada montagem deixa um widget para trás.
|
|
669
|
+
|
|
670
|
+
### Erro de importação em Next.js ou Nuxt
|
|
671
|
+
|
|
672
|
+
O pacote não toca o DOM ao ser importado, então o import em si é seguro. Mas
|
|
673
|
+
`new ILibrasWidget(...)` precisa do navegador: crie dentro de `useEffect`
|
|
674
|
+
(React) ou `onMounted` (Vue), nunca no corpo do componente.
|
|
675
|
+
|
|
676
|
+
## 🤝 Contribuindo
|
|
677
|
+
|
|
678
|
+
Contribuições são bem-vindas! Para contribuir:
|
|
679
|
+
|
|
680
|
+
1. Fork o projeto
|
|
681
|
+
2. Crie uma branch para sua feature (`git checkout -b feature/NovaFeature`)
|
|
682
|
+
3. Commit suas mudanças (`git commit -m 'Adiciona nova feature'`)
|
|
683
|
+
4. Push para a branch (`git push origin feature/NovaFeature`)
|
|
684
|
+
5. Abra um Pull Request
|
|
685
|
+
|
|
686
|
+
## 📝 Licença
|
|
687
|
+
|
|
688
|
+
Este projeto está sob a licença MIT. Veja o arquivo [LICENSE](LICENSE) para mais detalhes.
|
|
689
|
+
|
|
690
|
+
## 👥 Autor
|
|
691
|
+
|
|
692
|
+
**iLibras Team**
|
|
693
|
+
|
|
694
|
+
## 📧 Suporte
|
|
695
|
+
|
|
696
|
+
Para suporte, envie um email para suporte@ilibras.com.br ou abra uma issue no GitHub.
|
|
697
|
+
|
|
698
|
+
## 🔄 Changelog
|
|
699
|
+
|
|
700
|
+
### v1.0.0 - 2026-02-18
|
|
701
|
+
- ✨ Versão inicial
|
|
702
|
+
- 🎨 Widget flutuante com design responsivo
|
|
703
|
+
- ✅ Validação de CPF
|
|
704
|
+
- 📱 Suporte mobile
|
|
705
|
+
- 🎯 Máscara de entrada para CPF
|
|
706
|
+
- 🌐 Múltiplas opções de posicionamento
|
|
707
|
+
- 🎨 Personalização de cores e textos
|
|
708
|
+
- 🔒 Envio seguro via POST para API
|
|
709
|
+
- 📡 Integração com API iLibras (cadastrar_cliente.php)
|
|
710
|
+
- 🔄 Redirecionamento automático baseado na resposta da API
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
Feito com ❤️ pela equipe iLibras
|