@hgateam/chat-widget 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/README.md +83 -0
- package/package.json +44 -0
- package/public/widget.js +535 -0
- package/src/index.js +217 -0
- package/types/index.d.ts +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @ticktur/chat-widget
|
|
2
|
+
|
|
3
|
+
Widget de chat embebible con WebSocket en tiempo real para eventos de Ticktur.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
### Opción 1: Desde GitHub (Desarrollo)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install git+https://github.com/ticktur/events-front.git#dev-adrian:packages/chat-widget
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Opción 2: Uso Directo (CDN)
|
|
14
|
+
|
|
15
|
+
No requiere instalación. Solo incluye el script en tu HTML:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<!-- Widget Integration: Solo configurar el data-event-id y data-widget-hash -->
|
|
19
|
+
<script
|
|
20
|
+
src="http://localhost:5173/widget.js"
|
|
21
|
+
data-event-id="1"
|
|
22
|
+
data-widget-hash="f51c8b6939c6a86adf4df6f847e78ec1">
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Producción:**
|
|
27
|
+
```html
|
|
28
|
+
<script
|
|
29
|
+
src="https://cdn.ticktur.com/widget.js"
|
|
30
|
+
data-event-id="TU_EVENT_ID"
|
|
31
|
+
data-widget-hash="TU_HASH_WIDGET">
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Uso con NPM
|
|
36
|
+
|
|
37
|
+
Si instalaste desde npm, necesitas servir el archivo `widget.js`:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// En tu proyecto Node.js/Express
|
|
41
|
+
app.use('/widget', express.static('node_modules/@ticktur/chat-widget/public'));
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Luego en tu HTML:
|
|
45
|
+
```html
|
|
46
|
+
<script
|
|
47
|
+
src="/widget/widget.js"
|
|
48
|
+
data-event-id="1"
|
|
49
|
+
data-widget-hash="tu-hash-aqui">
|
|
50
|
+
</script>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Características
|
|
54
|
+
|
|
55
|
+
- Widget flotante en esquina inferior derecha
|
|
56
|
+
- Chat en tiempo real con WebSocket (Laravel Reverb)
|
|
57
|
+
- Totalmente aislado del CSS/JS del sitio host
|
|
58
|
+
- Responsive (móvil y desktop)
|
|
59
|
+
- Framework-agnostic (funciona en cualquier web)
|
|
60
|
+
|
|
61
|
+
## Configuración
|
|
62
|
+
|
|
63
|
+
El widget requiere 2 atributos:
|
|
64
|
+
|
|
65
|
+
- `data-event-id`: ID del evento en Ticktur
|
|
66
|
+
- `data-widget-hash`: Hash de autorización del widget (obtenido desde el dashboard)
|
|
67
|
+
|
|
68
|
+
## API Backend
|
|
69
|
+
|
|
70
|
+
El widget se conecta a:
|
|
71
|
+
- API de validación: `GET /api/widgets/validate/{hash}`
|
|
72
|
+
- WebSocket: Laravel Reverb en puerto 8080
|
|
73
|
+
- Canal público: `widget.{hash}`
|
|
74
|
+
|
|
75
|
+
## Eventos WebSocket
|
|
76
|
+
|
|
77
|
+
El widget escucha:
|
|
78
|
+
- `WidgetStatusChanged`: Cuando se activa/desactiva el widget
|
|
79
|
+
- `WidgetMessageReceived`: Nuevos mensajes del soporte
|
|
80
|
+
|
|
81
|
+
## Soporte
|
|
82
|
+
|
|
83
|
+
Para más información: https://github.com/ticktur/events-front
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hgateam/chat-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Widget de chat embebible con WebSocket para eventos de Ticktur",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"module": "src/index.js",
|
|
7
|
+
"types": "types/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src/",
|
|
10
|
+
"public/widget.js",
|
|
11
|
+
"types/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"INSTALL.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"No tests configured\" && exit 0"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/ticktur/events-front.git",
|
|
21
|
+
"directory": "packages/chat-widget"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"chat",
|
|
25
|
+
"widget",
|
|
26
|
+
"embeddable",
|
|
27
|
+
"websocket",
|
|
28
|
+
"ticktur",
|
|
29
|
+
"vue",
|
|
30
|
+
"react",
|
|
31
|
+
"webpack",
|
|
32
|
+
"vite"
|
|
33
|
+
],
|
|
34
|
+
"author": "Ticktur",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"pusher-js": "^8.2.0",
|
|
38
|
+
"laravel-echo": "^1.15.3"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/ticktur/events-front/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/ticktur/events-front/tree/dev-adrian/packages/chat-widget#readme"
|
|
44
|
+
}
|
package/public/widget.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticktur Chat Widget - Versión Embebible
|
|
3
|
+
*
|
|
4
|
+
* Uso:
|
|
5
|
+
* <script src="https://chat.ticktur.com/widget.js" data-event-id="1"></script>
|
|
6
|
+
*
|
|
7
|
+
* Funcionalidad:
|
|
8
|
+
* - Crea un botón flotante verde (#10b981) en la esquina inferior derecha
|
|
9
|
+
* - Al hacer clic, abre una ventana de chat en iFrame
|
|
10
|
+
* - Totalmente aislado del CSS/JS del sitio host
|
|
11
|
+
* - Framework-agnostic (funciona en cualquier web)
|
|
12
|
+
*
|
|
13
|
+
* Futuro (Fase 2):
|
|
14
|
+
* - Autenticación con API key usando MD5
|
|
15
|
+
* - data-api-key="tu-clave-md5"
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
(function () {
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
// Configuración
|
|
22
|
+
const CONFIG = {
|
|
23
|
+
BASE_URL: 'http://localhost:5173', // Cambiar en producción a https://chat.ticktur.com
|
|
24
|
+
BUTTON_COLOR: '#10b981',
|
|
25
|
+
BUTTON_HOVER_COLOR: '#059669',
|
|
26
|
+
WIDGET_WIDTH: '400px',
|
|
27
|
+
WIDGET_HEIGHT: '600px',
|
|
28
|
+
WIDGET_WIDTH_MOBILE: '100%',
|
|
29
|
+
WIDGET_HEIGHT_MOBILE: '100%',
|
|
30
|
+
Z_INDEX: '9999'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Obtener el script tag actual
|
|
34
|
+
const currentScript = document.currentScript || document.querySelector('script[data-event-id]');
|
|
35
|
+
|
|
36
|
+
if (!currentScript) {
|
|
37
|
+
console.error('[Ticktur Widget] No se encontró el script tag con data-event-id');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extraer configuración del script tag
|
|
42
|
+
const eventId = currentScript.getAttribute('data-event-id');
|
|
43
|
+
const widgetHash = currentScript.getAttribute('data-widget-hash');
|
|
44
|
+
|
|
45
|
+
if (!eventId) {
|
|
46
|
+
console.error('[Ticktur Widget] Falta el atributo data-event-id en el script tag');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!widgetHash) {
|
|
51
|
+
console.error('[Ticktur Widget] Falta el atributo data-widget-hash en el script tag');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('[Ticktur Widget] Inicializando widget...');
|
|
56
|
+
console.log('[Ticktur Widget] Hash:', widgetHash);
|
|
57
|
+
console.log('[Ticktur Widget] Evento:', eventId);
|
|
58
|
+
|
|
59
|
+
// Variable para almacenar datos del widget validado
|
|
60
|
+
let widgetData = null;
|
|
61
|
+
|
|
62
|
+
// Validar widget contra la base de datos usando endpoint de autorización
|
|
63
|
+
async function validateWidget() {
|
|
64
|
+
try {
|
|
65
|
+
const apiUrl = CONFIG.BASE_URL.replace(':5173', ':8000');
|
|
66
|
+
|
|
67
|
+
const response = await fetch(`${apiUrl}/api/widgets/validate/${widgetHash}`);
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
console.error('[Ticktur Widget] Autorización denegada. Status:', response.status);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
|
|
76
|
+
if (!data.success) {
|
|
77
|
+
console.error('[Ticktur Widget] Widget no autorizado');
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
widgetData = data.data;
|
|
82
|
+
|
|
83
|
+
console.log('[Ticktur Widget] Widget autorizado correctamente');
|
|
84
|
+
console.log('[Ticktur Widget] Nombre:', widgetData.widget_name);
|
|
85
|
+
console.log('[Ticktur Widget] Evento ID:', widgetData.event_id);
|
|
86
|
+
console.log('[Ticktur Widget] Estado:', widgetData.is_active ? 'Activo' : 'Inactivo');
|
|
87
|
+
return true;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[Ticktur Widget] Error al validar widget:', error);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Estado del widget
|
|
95
|
+
let widgetState = {
|
|
96
|
+
isOpen: false,
|
|
97
|
+
isMinimized: false,
|
|
98
|
+
hasUnreadMessages: false
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Crear contenedor principal del widget
|
|
102
|
+
function createWidgetContainer() {
|
|
103
|
+
const container = document.createElement('div');
|
|
104
|
+
container.id = 'ticktur-widget-container';
|
|
105
|
+
container.style.cssText = `
|
|
106
|
+
position: fixed;
|
|
107
|
+
bottom: 20px;
|
|
108
|
+
right: 20px;
|
|
109
|
+
z-index: ${CONFIG.Z_INDEX};
|
|
110
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
111
|
+
`;
|
|
112
|
+
return container;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Crear botón flotante
|
|
116
|
+
function createFloatingButton() {
|
|
117
|
+
const button = document.createElement('button');
|
|
118
|
+
button.id = 'ticktur-widget-button';
|
|
119
|
+
button.setAttribute('aria-label', 'Abrir chat de soporte');
|
|
120
|
+
button.innerHTML = `
|
|
121
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
122
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
123
|
+
</svg>
|
|
124
|
+
`;
|
|
125
|
+
button.style.cssText = `
|
|
126
|
+
width: 60px;
|
|
127
|
+
height: 60px;
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: linear-gradient(135deg, ${CONFIG.BUTTON_COLOR} 0%, ${CONFIG.BUTTON_HOVER_COLOR} 100%);
|
|
130
|
+
border: none;
|
|
131
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
color: white;
|
|
137
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
138
|
+
outline: none;
|
|
139
|
+
position: relative;
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
// Badge de notificación (sin usar inicialmente)
|
|
143
|
+
const badge = document.createElement('span');
|
|
144
|
+
badge.id = 'ticktur-widget-badge';
|
|
145
|
+
badge.style.cssText = `
|
|
146
|
+
position: absolute;
|
|
147
|
+
top: -2px;
|
|
148
|
+
right: -2px;
|
|
149
|
+
width: 20px;
|
|
150
|
+
height: 20px;
|
|
151
|
+
background: #ef4444;
|
|
152
|
+
border-radius: 50%;
|
|
153
|
+
border: 2px solid white;
|
|
154
|
+
font-size: 11px;
|
|
155
|
+
font-weight: bold;
|
|
156
|
+
display: none;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: center;
|
|
159
|
+
color: white;
|
|
160
|
+
`;
|
|
161
|
+
badge.textContent = '1';
|
|
162
|
+
button.appendChild(badge);
|
|
163
|
+
|
|
164
|
+
// Hover effect
|
|
165
|
+
button.addEventListener('mouseenter', () => {
|
|
166
|
+
button.style.transform = 'scale(1.1)';
|
|
167
|
+
button.style.boxShadow = '0 6px 20px rgba(16, 185, 129, 0.5)';
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
button.addEventListener('mouseleave', () => {
|
|
171
|
+
button.style.transform = 'scale(1)';
|
|
172
|
+
button.style.boxShadow = '0 4px 12px rgba(16, 185, 129, 0.4)';
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Click event
|
|
176
|
+
button.addEventListener('click', toggleWidget);
|
|
177
|
+
|
|
178
|
+
return button;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Crear ventana de chat (iFrame)
|
|
182
|
+
function createChatWindow() {
|
|
183
|
+
const chatWindow = document.createElement('div');
|
|
184
|
+
chatWindow.id = 'ticktur-widget-window';
|
|
185
|
+
chatWindow.style.cssText = `
|
|
186
|
+
position: fixed;
|
|
187
|
+
bottom: 100px;
|
|
188
|
+
right: 20px;
|
|
189
|
+
width: ${CONFIG.WIDGET_WIDTH};
|
|
190
|
+
height: ${CONFIG.WIDGET_HEIGHT};
|
|
191
|
+
background: white;
|
|
192
|
+
border-radius: 12px;
|
|
193
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
194
|
+
overflow: hidden;
|
|
195
|
+
display: none;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
z-index: ${CONFIG.Z_INDEX};
|
|
198
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
199
|
+
transform-origin: bottom right;
|
|
200
|
+
`;
|
|
201
|
+
|
|
202
|
+
// iFrame del chat
|
|
203
|
+
const iframe = document.createElement('iframe');
|
|
204
|
+
iframe.id = 'ticktur-widget-iframe';
|
|
205
|
+
iframe.src = `${CONFIG.BASE_URL}/embed/${eventId}`;
|
|
206
|
+
iframe.style.cssText = `
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
border: none;
|
|
210
|
+
display: block;
|
|
211
|
+
`;
|
|
212
|
+
iframe.setAttribute('allow', 'microphone; camera'); // Por si en el futuro se agrega video/audio
|
|
213
|
+
iframe.setAttribute('title', 'Chat de Soporte Ticktur');
|
|
214
|
+
|
|
215
|
+
chatWindow.appendChild(iframe);
|
|
216
|
+
|
|
217
|
+
// Responsive: fullscreen en móviles
|
|
218
|
+
if (window.innerWidth <= 768) {
|
|
219
|
+
chatWindow.style.cssText = `
|
|
220
|
+
position: fixed;
|
|
221
|
+
top: 0;
|
|
222
|
+
left: 0;
|
|
223
|
+
right: 0;
|
|
224
|
+
bottom: 0;
|
|
225
|
+
width: ${CONFIG.WIDGET_WIDTH_MOBILE};
|
|
226
|
+
height: ${CONFIG.WIDGET_HEIGHT_MOBILE};
|
|
227
|
+
border-radius: 0;
|
|
228
|
+
z-index: ${CONFIG.Z_INDEX};
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return chatWindow;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Toggle del widget (abrir/cerrar)
|
|
236
|
+
function toggleWidget() {
|
|
237
|
+
const chatWindow = document.getElementById('ticktur-widget-window');
|
|
238
|
+
const button = document.getElementById('ticktur-widget-button');
|
|
239
|
+
const badge = document.getElementById('ticktur-widget-badge');
|
|
240
|
+
|
|
241
|
+
if (!chatWindow) return;
|
|
242
|
+
|
|
243
|
+
widgetState.isOpen = !widgetState.isOpen;
|
|
244
|
+
|
|
245
|
+
if (widgetState.isOpen) {
|
|
246
|
+
// Abrir
|
|
247
|
+
chatWindow.style.display = 'flex';
|
|
248
|
+
chatWindow.style.transform = 'scale(1)';
|
|
249
|
+
chatWindow.style.opacity = '1';
|
|
250
|
+
button.style.transform = 'rotate(90deg) scale(1.1)';
|
|
251
|
+
|
|
252
|
+
// Ocultar badge al abrir
|
|
253
|
+
if (badge) {
|
|
254
|
+
badge.style.display = 'none';
|
|
255
|
+
widgetState.hasUnreadMessages = false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Cambiar icono a X
|
|
259
|
+
button.innerHTML = `
|
|
260
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
261
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
262
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
263
|
+
</svg>
|
|
264
|
+
`;
|
|
265
|
+
} else {
|
|
266
|
+
// Cerrar
|
|
267
|
+
chatWindow.style.transform = 'scale(0.8)';
|
|
268
|
+
chatWindow.style.opacity = '0';
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
chatWindow.style.display = 'none';
|
|
271
|
+
}, 300);
|
|
272
|
+
button.style.transform = 'rotate(0deg) scale(1)';
|
|
273
|
+
|
|
274
|
+
// Restaurar icono de chat
|
|
275
|
+
button.innerHTML = `
|
|
276
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
277
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
278
|
+
</svg>
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Escuchar mensajes del iFrame (para notificaciones)
|
|
284
|
+
function setupMessageListener() {
|
|
285
|
+
window.addEventListener('message', (event) => {
|
|
286
|
+
// Validar origen (en producción debe ser estricto)
|
|
287
|
+
if (event.origin !== CONFIG.BASE_URL && !CONFIG.BASE_URL.includes('localhost')) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const data = event.data;
|
|
292
|
+
|
|
293
|
+
// Evento: Nuevo mensaje recibido
|
|
294
|
+
if (data.type === 'NEW_MESSAGE' && !widgetState.isOpen) {
|
|
295
|
+
const badge = document.getElementById('ticktur-widget-badge');
|
|
296
|
+
if (badge) {
|
|
297
|
+
badge.style.display = 'flex';
|
|
298
|
+
widgetState.hasUnreadMessages = true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Evento: Mensaje enviado
|
|
303
|
+
if (data.type === 'MESSAGE_SENT') {
|
|
304
|
+
showNotification('Mensaje enviado correctamente');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Evento: Chat cerrado/resuelto
|
|
308
|
+
if (data.type === 'CHAT_ENDED') {
|
|
309
|
+
console.log('[Ticktur Widget] Conversación finalizada');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Evento: Solicitud de cerrar widget
|
|
313
|
+
if (data.type === 'CLOSE_WIDGET') {
|
|
314
|
+
if (widgetState.isOpen) {
|
|
315
|
+
toggleWidget();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Manejar resize de la ventana
|
|
322
|
+
function handleResize() {
|
|
323
|
+
const chatWindow = document.getElementById('ticktur-widget-window');
|
|
324
|
+
if (!chatWindow) return;
|
|
325
|
+
|
|
326
|
+
if (window.innerWidth <= 768) {
|
|
327
|
+
chatWindow.style.cssText = `
|
|
328
|
+
position: fixed;
|
|
329
|
+
top: 0;
|
|
330
|
+
left: 0;
|
|
331
|
+
right: 0;
|
|
332
|
+
bottom: 0;
|
|
333
|
+
width: 100%;
|
|
334
|
+
height: 100%;
|
|
335
|
+
border-radius: 0;
|
|
336
|
+
z-index: ${CONFIG.Z_INDEX};
|
|
337
|
+
${widgetState.isOpen ? 'display: flex;' : 'display: none;'}
|
|
338
|
+
`;
|
|
339
|
+
} else {
|
|
340
|
+
chatWindow.style.cssText = `
|
|
341
|
+
position: fixed;
|
|
342
|
+
bottom: 100px;
|
|
343
|
+
right: 20px;
|
|
344
|
+
width: ${CONFIG.WIDGET_WIDTH};
|
|
345
|
+
height: ${CONFIG.WIDGET_HEIGHT};
|
|
346
|
+
background: white;
|
|
347
|
+
border-radius: 12px;
|
|
348
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
349
|
+
overflow: hidden;
|
|
350
|
+
z-index: ${CONFIG.Z_INDEX};
|
|
351
|
+
${widgetState.isOpen ? 'display: flex;' : 'display: none;'}
|
|
352
|
+
`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// WebSocket: Cargar dependencias y conectar
|
|
357
|
+
function initializeWebSocket() {
|
|
358
|
+
// Reverb usa Pusher JS como cliente
|
|
359
|
+
if (!window.Pusher) {
|
|
360
|
+
const pusherScript = document.createElement('script');
|
|
361
|
+
pusherScript.src = 'https://js.pusher.com/8.2.0/pusher.min.js';
|
|
362
|
+
pusherScript.onload = () => {
|
|
363
|
+
loadLaravelEcho();
|
|
364
|
+
};
|
|
365
|
+
pusherScript.onerror = () => {
|
|
366
|
+
console.warn('[Ticktur Widget] Error al cargar Pusher (cliente de Reverb)');
|
|
367
|
+
};
|
|
368
|
+
document.head.appendChild(pusherScript);
|
|
369
|
+
} else {
|
|
370
|
+
loadLaravelEcho();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Cargar Laravel Echo
|
|
375
|
+
function loadLaravelEcho() {
|
|
376
|
+
if (!window.Echo) {
|
|
377
|
+
const echoScript = document.createElement('script');
|
|
378
|
+
echoScript.src = 'https://cdn.jsdelivr.net/npm/laravel-echo@1.15.3/dist/echo.iife.js';
|
|
379
|
+
echoScript.onload = () => {
|
|
380
|
+
setupEcho();
|
|
381
|
+
};
|
|
382
|
+
echoScript.onerror = () => {
|
|
383
|
+
console.error('[Ticktur Widget] Error al cargar Laravel Echo');
|
|
384
|
+
};
|
|
385
|
+
document.head.appendChild(echoScript);
|
|
386
|
+
} else {
|
|
387
|
+
setupEcho();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Configurar Echo con Reverb (usa pusher como broadcaster)
|
|
392
|
+
function setupEcho() {
|
|
393
|
+
try {
|
|
394
|
+
const echo = new Echo({
|
|
395
|
+
broadcaster: 'pusher',
|
|
396
|
+
key: 'juunjgece3udni3rfg4j',
|
|
397
|
+
wsHost: '127.0.0.1',
|
|
398
|
+
wsPort: 8080,
|
|
399
|
+
wssPort: 8080,
|
|
400
|
+
forceTLS: false,
|
|
401
|
+
enabledTransports: ['ws', 'wss'],
|
|
402
|
+
disableStats: true,
|
|
403
|
+
cluster: '',
|
|
404
|
+
encrypted: false
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
console.log('[Ticktur Widget] WebSocket conectado');
|
|
408
|
+
|
|
409
|
+
// Suscribirse al canal público del widget
|
|
410
|
+
if (widgetData && widgetData.hash) {
|
|
411
|
+
try {
|
|
412
|
+
echo.channel(`widget.${widgetData.hash}`)
|
|
413
|
+
.listen('WidgetStatusChanged', (data) => {
|
|
414
|
+
console.log('[Ticktur Widget] Estado actualizado:', data);
|
|
415
|
+
if (!data.is_active) {
|
|
416
|
+
showNotification('El chat ha sido desactivado');
|
|
417
|
+
// Opcional: cerrar el widget
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
.listen('WidgetMessageReceived', (data) => {
|
|
421
|
+
console.log('[Ticktur Widget] Nuevo mensaje recibido:', data);
|
|
422
|
+
showNotification('Nuevo mensaje');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
console.log('[Ticktur Widget] Suscrito al canal: widget.' + widgetData.hash);
|
|
426
|
+
} catch (channelError) {
|
|
427
|
+
console.warn('[Ticktur Widget] No se pudo suscribir al canal:', channelError);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Guardar instancia para limpieza futura
|
|
432
|
+
window.tickturEcho = echo;
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.warn('[Ticktur Widget] WebSocket no disponible:', error.message);
|
|
435
|
+
console.log('[Ticktur Widget] El widget funcionará sin actualizaciones en tiempo real');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Mostrar notificación visual
|
|
440
|
+
function showNotification(message) {
|
|
441
|
+
const badge = document.getElementById('ticktur-widget-badge');
|
|
442
|
+
if (badge && !widgetState.isOpen) {
|
|
443
|
+
badge.style.display = 'flex';
|
|
444
|
+
badge.textContent = '!';
|
|
445
|
+
widgetState.hasUnreadMessages = true;
|
|
446
|
+
}
|
|
447
|
+
console.log(`[Ticktur Widget] ${message}`);
|
|
448
|
+
}
|
|
449
|
+
// Actualizar información del widget en el HTML de la página
|
|
450
|
+
function updateWidgetInfo() {
|
|
451
|
+
if (!widgetData) return;
|
|
452
|
+
|
|
453
|
+
// Actualizar event ID
|
|
454
|
+
const eventIdElement = document.getElementById('ticktur-event-id');
|
|
455
|
+
if (eventIdElement) {
|
|
456
|
+
eventIdElement.textContent = widgetData.event_id || eventId;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Actualizar estado del widget
|
|
460
|
+
const statusElements = document.querySelectorAll('[id^="ticktur-widget-status"]');
|
|
461
|
+
statusElements.forEach((element) => {
|
|
462
|
+
if (widgetData.is_active) {
|
|
463
|
+
element.textContent = 'Widget Activo';
|
|
464
|
+
element.style.color = '#10b981';
|
|
465
|
+
} else {
|
|
466
|
+
element.textContent = 'Widget Inactivo';
|
|
467
|
+
element.style.color = '#ef4444';
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Actualizar indicador visual de estado
|
|
472
|
+
const statusIndicators = document.querySelectorAll('.ticktur-status-indicator');
|
|
473
|
+
statusIndicators.forEach((indicator) => {
|
|
474
|
+
indicator.textContent = widgetData.is_active ? '✓' : '✕';
|
|
475
|
+
indicator.style.color = widgetData.is_active ? '#10b981' : '#ef4444';
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
console.log('[Ticktur Widget] Información actualizada en la página');
|
|
479
|
+
}
|
|
480
|
+
// Inicializar widget
|
|
481
|
+
async function initWidget() {
|
|
482
|
+
// Verificar que el DOM esté listo
|
|
483
|
+
if (document.readyState === 'loading') {
|
|
484
|
+
document.addEventListener('DOMContentLoaded', initWidget);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
console.log('[Ticktur Widget] DOM listo, validando widget...');
|
|
489
|
+
|
|
490
|
+
// Validar widget antes de continuar
|
|
491
|
+
const isValid = await validateWidget();
|
|
492
|
+
|
|
493
|
+
if (!isValid) {
|
|
494
|
+
console.error('[Ticktur Widget] Widget no autorizado. Verifica:');
|
|
495
|
+
console.error(' 1. Que el evento exista');
|
|
496
|
+
console.error(' 2. Que el hash sea correcto');
|
|
497
|
+
console.error(' 3. Que el widget esté activo (is_active = true)');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
console.log('[Ticktur Widget] Montando componentes...');
|
|
502
|
+
|
|
503
|
+
// Actualizar información en el HTML
|
|
504
|
+
updateWidgetInfo();
|
|
505
|
+
|
|
506
|
+
// Crear elementos
|
|
507
|
+
const container = createWidgetContainer();
|
|
508
|
+
const button = createFloatingButton();
|
|
509
|
+
const chatWindow = createChatWindow();
|
|
510
|
+
|
|
511
|
+
// Montar en el DOM
|
|
512
|
+
container.appendChild(button);
|
|
513
|
+
document.body.appendChild(container);
|
|
514
|
+
document.body.appendChild(chatWindow);
|
|
515
|
+
|
|
516
|
+
// Setup listeners
|
|
517
|
+
setupMessageListener();
|
|
518
|
+
window.addEventListener('resize', handleResize);
|
|
519
|
+
|
|
520
|
+
// Inicializar WebSocket
|
|
521
|
+
initializeWebSocket();
|
|
522
|
+
|
|
523
|
+
console.log('[Ticktur Widget] Widget inicializado correctamente');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Limpieza al cerrar/recargar
|
|
527
|
+
window.addEventListener('beforeunload', () => {
|
|
528
|
+
if (window.tickturEcho) {
|
|
529
|
+
window.tickturEcho.disconnect();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Auto-inicializar
|
|
534
|
+
initWidget();
|
|
535
|
+
})();
|
package/src/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticktur Chat Widget - Entry Point
|
|
3
|
+
* Exporta función para inicialización programática
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class TickturWidget {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.config = {
|
|
9
|
+
eventId: config.eventId || null,
|
|
10
|
+
widgetHash: config.widgetHash || null,
|
|
11
|
+
baseUrl: config.baseUrl || 'http://localhost:5173',
|
|
12
|
+
buttonColor: config.buttonColor || '#10b981',
|
|
13
|
+
buttonHoverColor: config.buttonHoverColor || '#059669',
|
|
14
|
+
widgetWidth: config.widgetWidth || '400px',
|
|
15
|
+
widgetHeight: config.widgetHeight || '600px',
|
|
16
|
+
zIndex: config.zIndex || '9999',
|
|
17
|
+
...config
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.widgetState = {
|
|
21
|
+
isOpen: false,
|
|
22
|
+
isMinimized: false,
|
|
23
|
+
hasUnreadMessages: false
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.elements = {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inicializa el widget
|
|
31
|
+
*/
|
|
32
|
+
async init() {
|
|
33
|
+
if (!this.config.eventId || !this.config.widgetHash) {
|
|
34
|
+
throw new Error('[Ticktur Widget] eventId y widgetHash son obligatorios');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('[Ticktur Widget] Inicializando...', this.config);
|
|
38
|
+
|
|
39
|
+
// Validar widget
|
|
40
|
+
const isValid = await this.validateWidget();
|
|
41
|
+
if (!isValid) {
|
|
42
|
+
throw new Error('[Ticktur Widget] Autorización denegada');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Crear elementos del widget
|
|
46
|
+
this.createButton();
|
|
47
|
+
this.createWidgetContainer();
|
|
48
|
+
this.attachEventListeners();
|
|
49
|
+
|
|
50
|
+
console.log('[Ticktur Widget] Inicializado correctamente');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Valida el widget contra la API
|
|
55
|
+
*/
|
|
56
|
+
async validateWidget() {
|
|
57
|
+
try {
|
|
58
|
+
const apiUrl = this.config.baseUrl.replace(':5173', ':8000');
|
|
59
|
+
const response = await fetch(`${apiUrl}/api/widgets/validate/${this.config.widgetHash}`);
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
console.error('[Ticktur Widget] Autorización denegada. Status:', response.status);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const data = await response.json();
|
|
67
|
+
|
|
68
|
+
if (!data.success) {
|
|
69
|
+
console.error('[Ticktur Widget] Widget no autorizado');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.widgetData = data.data;
|
|
74
|
+
console.log('[Ticktur Widget] Widget autorizado:', this.widgetData.widget_name);
|
|
75
|
+
return true;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('[Ticktur Widget] Error al validar:', error);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Crea el botón flotante
|
|
84
|
+
*/
|
|
85
|
+
createButton() {
|
|
86
|
+
const button = document.createElement('button');
|
|
87
|
+
button.id = 'ticktur-chat-button';
|
|
88
|
+
button.innerHTML = `
|
|
89
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
90
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
91
|
+
</svg>
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
button.style.cssText = `
|
|
95
|
+
position: fixed;
|
|
96
|
+
bottom: 24px;
|
|
97
|
+
right: 24px;
|
|
98
|
+
width: 56px;
|
|
99
|
+
height: 56px;
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
background-color: ${this.config.buttonColor};
|
|
102
|
+
color: white;
|
|
103
|
+
border: none;
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
106
|
+
z-index: ${this.config.zIndex};
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
transition: all 0.3s ease;
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
button.onmouseover = () => {
|
|
114
|
+
button.style.backgroundColor = this.config.buttonHoverColor;
|
|
115
|
+
button.style.transform = 'scale(1.1)';
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
button.onmouseout = () => {
|
|
119
|
+
button.style.backgroundColor = this.config.buttonColor;
|
|
120
|
+
button.style.transform = 'scale(1)';
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
document.body.appendChild(button);
|
|
124
|
+
this.elements.button = button;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Crea el contenedor del widget
|
|
129
|
+
*/
|
|
130
|
+
createWidgetContainer() {
|
|
131
|
+
const container = document.createElement('div');
|
|
132
|
+
container.id = 'ticktur-widget-container';
|
|
133
|
+
container.style.cssText = `
|
|
134
|
+
position: fixed;
|
|
135
|
+
bottom: 96px;
|
|
136
|
+
right: 24px;
|
|
137
|
+
width: ${this.config.widgetWidth};
|
|
138
|
+
height: ${this.config.widgetHeight};
|
|
139
|
+
background: white;
|
|
140
|
+
border-radius: 12px;
|
|
141
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
142
|
+
z-index: ${this.config.zIndex};
|
|
143
|
+
display: none;
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
const iframe = document.createElement('iframe');
|
|
148
|
+
iframe.src = `${this.config.baseUrl}/?eventId=${this.config.eventId}&hash=${this.config.widgetHash}`;
|
|
149
|
+
iframe.style.cssText = `
|
|
150
|
+
width: 100%;
|
|
151
|
+
height: 100%;
|
|
152
|
+
border: none;
|
|
153
|
+
`;
|
|
154
|
+
|
|
155
|
+
container.appendChild(iframe);
|
|
156
|
+
document.body.appendChild(container);
|
|
157
|
+
this.elements.container = container;
|
|
158
|
+
this.elements.iframe = iframe;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Adjunta event listeners
|
|
163
|
+
*/
|
|
164
|
+
attachEventListeners() {
|
|
165
|
+
this.elements.button.addEventListener('click', () => {
|
|
166
|
+
this.toggle();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Alterna visibilidad del widget
|
|
172
|
+
*/
|
|
173
|
+
toggle() {
|
|
174
|
+
this.widgetState.isOpen = !this.widgetState.isOpen;
|
|
175
|
+
this.elements.container.style.display = this.widgetState.isOpen ? 'block' : 'none';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Abre el widget
|
|
180
|
+
*/
|
|
181
|
+
open() {
|
|
182
|
+
this.widgetState.isOpen = true;
|
|
183
|
+
this.elements.container.style.display = 'block';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Cierra el widget
|
|
188
|
+
*/
|
|
189
|
+
close() {
|
|
190
|
+
this.widgetState.isOpen = false;
|
|
191
|
+
this.elements.container.style.display = 'none';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Destruye el widget
|
|
196
|
+
*/
|
|
197
|
+
destroy() {
|
|
198
|
+
if (this.elements.button) {
|
|
199
|
+
this.elements.button.remove();
|
|
200
|
+
}
|
|
201
|
+
if (this.elements.container) {
|
|
202
|
+
this.elements.container.remove();
|
|
203
|
+
}
|
|
204
|
+
this.elements = {};
|
|
205
|
+
this.widgetState = { isOpen: false, isMinimized: false, hasUnreadMessages: false };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Exportar para uso como módulo
|
|
210
|
+
export default TickturWidget;
|
|
211
|
+
|
|
212
|
+
// También exportar función helper
|
|
213
|
+
export function createWidget(config) {
|
|
214
|
+
const widget = new TickturWidget(config);
|
|
215
|
+
widget.init();
|
|
216
|
+
return widget;
|
|
217
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuración del widget de Ticktur
|
|
3
|
+
*/
|
|
4
|
+
export interface TickturWidgetConfig {
|
|
5
|
+
/** ID del evento (obligatorio) */
|
|
6
|
+
eventId: number | string;
|
|
7
|
+
/** Hash MD5 de autorización (obligatorio) */
|
|
8
|
+
widgetHash: string;
|
|
9
|
+
/** URL base de la API (default: http://localhost:5173) */
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
/** Color del botón (default: #10b981) */
|
|
12
|
+
buttonColor?: string;
|
|
13
|
+
/** Color del botón al hover (default: #059669) */
|
|
14
|
+
buttonHoverColor?: string;
|
|
15
|
+
/** Ancho del widget (default: 400px) */
|
|
16
|
+
widgetWidth?: string;
|
|
17
|
+
/** Alto del widget (default: 600px) */
|
|
18
|
+
widgetHeight?: string;
|
|
19
|
+
/** Z-index del widget (default: 9999) */
|
|
20
|
+
zIndex?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Estado del widget
|
|
25
|
+
*/
|
|
26
|
+
export interface WidgetState {
|
|
27
|
+
isOpen: boolean;
|
|
28
|
+
isMinimized: boolean;
|
|
29
|
+
hasUnreadMessages: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Datos del widget validado
|
|
34
|
+
*/
|
|
35
|
+
export interface WidgetData {
|
|
36
|
+
widget_name: string;
|
|
37
|
+
event_id: number;
|
|
38
|
+
is_active: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clase principal del widget de Ticktur
|
|
43
|
+
*/
|
|
44
|
+
export default class TickturWidget {
|
|
45
|
+
config: TickturWidgetConfig;
|
|
46
|
+
widgetState: WidgetState;
|
|
47
|
+
widgetData?: WidgetData;
|
|
48
|
+
elements: {
|
|
49
|
+
button?: HTMLButtonElement;
|
|
50
|
+
container?: HTMLDivElement;
|
|
51
|
+
iframe?: HTMLIFrameElement;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
constructor(config: TickturWidgetConfig);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Inicializa el widget
|
|
58
|
+
*/
|
|
59
|
+
init(): Promise<void>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Valida el widget contra la API
|
|
63
|
+
*/
|
|
64
|
+
validateWidget(): Promise<boolean>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Abre el widget
|
|
68
|
+
*/
|
|
69
|
+
open(): void;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Cierra el widget
|
|
73
|
+
*/
|
|
74
|
+
close(): void;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Alterna la visibilidad del widget
|
|
78
|
+
*/
|
|
79
|
+
toggle(): void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Destruye el widget y limpia el DOM
|
|
83
|
+
*/
|
|
84
|
+
destroy(): void;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Función helper para crear e inicializar el widget
|
|
89
|
+
*/
|
|
90
|
+
export function createWidget(config: TickturWidgetConfig): TickturWidget;
|