slicejs-web-framework 2.2.13 → 2.3.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/Slice/Components/Structural/ContextManager/ContextManager.js +361 -0
- package/Slice/Components/Structural/Controller/Controller.js +925 -890
- package/Slice/Components/Structural/EventManager/EventManager.js +329 -0
- package/Slice/Components/Structural/Router/Router.js +598 -589
- package/Slice/Slice.js +297 -249
- package/opencode.json +11 -0
- package/package.json +1 -1
- package/src/sliceConfig.json +60 -54
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventManager - Sistema de eventos pub/sub para Slice.js
|
|
3
|
+
* Ubicación: /Slice/Components/Structural/EventManager/EventManager.js
|
|
4
|
+
*
|
|
5
|
+
* Características:
|
|
6
|
+
* - Suscripciones globales y vinculadas a componentes
|
|
7
|
+
* - Auto-limpieza cuando componentes se destruyen
|
|
8
|
+
* - API simple: subscribe, subscribeOnce, unsubscribe, emit
|
|
9
|
+
*/
|
|
10
|
+
export default class EventManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
// Map<eventName, Map<subscriptionId, { callback, componentSliceId, once }>>
|
|
13
|
+
this.subscriptions = new Map();
|
|
14
|
+
|
|
15
|
+
// Map<sliceId, Set<{ eventName, subscriptionId }>> - Para auto-cleanup
|
|
16
|
+
this.componentSubscriptions = new Map();
|
|
17
|
+
|
|
18
|
+
// Contador para IDs únicos
|
|
19
|
+
this.idCounter = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
init() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================
|
|
27
|
+
// API PRINCIPAL
|
|
28
|
+
// ============================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Suscribirse a un evento
|
|
32
|
+
* @param {string} eventName - Nombre del evento
|
|
33
|
+
* @param {Function} callback - Función a ejecutar cuando se emita el evento
|
|
34
|
+
* @param {Object} options - Opciones: { component: SliceComponent }
|
|
35
|
+
* @returns {string} subscriptionId - ID para desuscribirse
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Suscripción global
|
|
39
|
+
* const id = slice.events.subscribe('user:login', (user) => {
|
|
40
|
+
* console.log('Usuario:', user);
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Suscripción con auto-cleanup
|
|
44
|
+
* slice.events.subscribe('user:login', (user) => {
|
|
45
|
+
* this.actualizar(user);
|
|
46
|
+
* }, { component: this });
|
|
47
|
+
*/
|
|
48
|
+
subscribe(eventName, callback, options = {}) {
|
|
49
|
+
if (typeof callback !== 'function') {
|
|
50
|
+
slice.logger.logError('EventManager', 'El callback debe ser una función');
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const subscriptionId = `evt_${++this.idCounter}`;
|
|
55
|
+
|
|
56
|
+
// Crear Map para este evento si no existe
|
|
57
|
+
if (!this.subscriptions.has(eventName)) {
|
|
58
|
+
this.subscriptions.set(eventName, new Map());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Guardar la suscripción
|
|
62
|
+
this.subscriptions.get(eventName).set(subscriptionId, {
|
|
63
|
+
callback,
|
|
64
|
+
componentSliceId: options.component?.sliceId || null,
|
|
65
|
+
once: false,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Si hay componente, registrar para auto-cleanup
|
|
69
|
+
if (options.component?.sliceId) {
|
|
70
|
+
this._registerComponentSubscription(options.component.sliceId, eventName, subscriptionId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
slice.logger.logInfo('EventManager', `Suscrito a "${eventName}" [${subscriptionId}]`);
|
|
74
|
+
|
|
75
|
+
return subscriptionId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Suscribirse a un evento una sola vez
|
|
80
|
+
* @param {string} eventName - Nombre del evento
|
|
81
|
+
* @param {Function} callback - Función a ejecutar
|
|
82
|
+
* @param {Object} options - Opciones: { component: SliceComponent }
|
|
83
|
+
* @returns {string} subscriptionId
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* slice.events.subscribeOnce('app:ready', () => {
|
|
87
|
+
* console.log('App lista!');
|
|
88
|
+
* });
|
|
89
|
+
*/
|
|
90
|
+
subscribeOnce(eventName, callback, options = {}) {
|
|
91
|
+
if (typeof callback !== 'function') {
|
|
92
|
+
slice.logger.logError('EventManager', 'El callback debe ser una función');
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const subscriptionId = `evt_${++this.idCounter}`;
|
|
97
|
+
|
|
98
|
+
if (!this.subscriptions.has(eventName)) {
|
|
99
|
+
this.subscriptions.set(eventName, new Map());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.subscriptions.get(eventName).set(subscriptionId, {
|
|
103
|
+
callback,
|
|
104
|
+
componentSliceId: options.component?.sliceId || null,
|
|
105
|
+
once: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (options.component?.sliceId) {
|
|
109
|
+
this._registerComponentSubscription(options.component.sliceId, eventName, subscriptionId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
slice.logger.logInfo('EventManager', `Suscrito (once) a "${eventName}" [${subscriptionId}]`);
|
|
113
|
+
|
|
114
|
+
return subscriptionId;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Desuscribirse de un evento
|
|
119
|
+
* @param {string} eventName - Nombre del evento
|
|
120
|
+
* @param {string} subscriptionId - ID de la suscripción
|
|
121
|
+
* @returns {boolean} true si se eliminó correctamente
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* const id = slice.events.subscribe('evento', callback);
|
|
125
|
+
* // Después...
|
|
126
|
+
* slice.events.unsubscribe('evento', id);
|
|
127
|
+
*/
|
|
128
|
+
unsubscribe(eventName, subscriptionId) {
|
|
129
|
+
if (!this.subscriptions.has(eventName)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const removed = this.subscriptions.get(eventName).delete(subscriptionId);
|
|
134
|
+
|
|
135
|
+
// Limpiar Map vacío
|
|
136
|
+
if (this.subscriptions.get(eventName).size === 0) {
|
|
137
|
+
this.subscriptions.delete(eventName);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (removed) {
|
|
141
|
+
slice.logger.logInfo('EventManager', `Desuscrito de "${eventName}" [${subscriptionId}]`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return removed;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Emitir un evento
|
|
149
|
+
* @param {string} eventName - Nombre del evento
|
|
150
|
+
* @param {*} data - Datos a pasar a los callbacks
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* slice.events.emit('user:login', { id: 123, name: 'Juan' });
|
|
154
|
+
* slice.events.emit('cart:cleared'); // Sin datos
|
|
155
|
+
*/
|
|
156
|
+
emit(eventName, data = null) {
|
|
157
|
+
slice.logger.logInfo('EventManager', `Emitiendo "${eventName}"`, data);
|
|
158
|
+
|
|
159
|
+
if (!this.subscriptions.has(eventName)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const toRemove = [];
|
|
164
|
+
|
|
165
|
+
// Notificar a todos los suscriptores
|
|
166
|
+
for (const [subscriptionId, subscription] of this.subscriptions.get(eventName)) {
|
|
167
|
+
// Verificar que el componente aún existe (si aplica)
|
|
168
|
+
if (subscription.componentSliceId) {
|
|
169
|
+
if (!slice.controller.activeComponents.has(subscription.componentSliceId)) {
|
|
170
|
+
// Componente ya no existe, marcar para eliminar
|
|
171
|
+
toRemove.push(subscriptionId);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Ejecutar callback
|
|
177
|
+
try {
|
|
178
|
+
subscription.callback(data);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
slice.logger.logError('EventManager', `Error en callback de "${eventName}" [${subscriptionId}]`, error);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Si es subscribeOnce, marcar para eliminar
|
|
184
|
+
if (subscription.once) {
|
|
185
|
+
toRemove.push(subscriptionId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Limpiar suscripciones marcadas
|
|
190
|
+
toRemove.forEach((id) => {
|
|
191
|
+
this.subscriptions.get(eventName).delete(id);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Limpiar Map vacío
|
|
195
|
+
if (this.subscriptions.get(eventName).size === 0) {
|
|
196
|
+
this.subscriptions.delete(eventName);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// BIND - Vinculación a Componente
|
|
202
|
+
// ============================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Vincular el EventManager a un componente para auto-cleanup
|
|
206
|
+
* @param {HTMLElement} component - Componente Slice con sliceId
|
|
207
|
+
* @returns {Object} API vinculada al componente
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* class MiComponente extends HTMLElement {
|
|
211
|
+
* async init() {
|
|
212
|
+
* this.events = slice.events.bind(this);
|
|
213
|
+
*
|
|
214
|
+
* this.events.subscribe('user:login', (user) => {
|
|
215
|
+
* this.actualizar(user);
|
|
216
|
+
* });
|
|
217
|
+
* }
|
|
218
|
+
* }
|
|
219
|
+
*/
|
|
220
|
+
bind(component) {
|
|
221
|
+
if (!component?.sliceId) {
|
|
222
|
+
slice.logger.logError('EventManager', 'bind() requiere un componente Slice válido con sliceId');
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const self = this;
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
/**
|
|
230
|
+
* Suscribirse a un evento (auto-cleanup)
|
|
231
|
+
*/
|
|
232
|
+
subscribe: (eventName, callback) => {
|
|
233
|
+
return self.subscribe(eventName, callback, { component });
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Suscribirse una sola vez (auto-cleanup)
|
|
238
|
+
*/
|
|
239
|
+
subscribeOnce: (eventName, callback) => {
|
|
240
|
+
return self.subscribeOnce(eventName, callback, { component });
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Emitir un evento
|
|
245
|
+
*/
|
|
246
|
+
emit: (eventName, data) => {
|
|
247
|
+
self.emit(eventName, data);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ============================================
|
|
253
|
+
// AUTO-CLEANUP
|
|
254
|
+
// ============================================
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Registrar suscripción para un componente (interno)
|
|
258
|
+
*/
|
|
259
|
+
_registerComponentSubscription(sliceId, eventName, subscriptionId) {
|
|
260
|
+
if (!this.componentSubscriptions.has(sliceId)) {
|
|
261
|
+
this.componentSubscriptions.set(sliceId, new Set());
|
|
262
|
+
}
|
|
263
|
+
this.componentSubscriptions.get(sliceId).add({ eventName, subscriptionId });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Limpiar todas las suscripciones de un componente
|
|
268
|
+
* Se llama automáticamente cuando el componente se destruye
|
|
269
|
+
* @param {string} sliceId - ID del componente
|
|
270
|
+
* @returns {number} Cantidad de suscripciones eliminadas
|
|
271
|
+
*/
|
|
272
|
+
cleanupComponent(sliceId) {
|
|
273
|
+
if (!this.componentSubscriptions.has(sliceId)) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const subscriptions = this.componentSubscriptions.get(sliceId);
|
|
278
|
+
let count = 0;
|
|
279
|
+
|
|
280
|
+
for (const { eventName, subscriptionId } of subscriptions) {
|
|
281
|
+
if (this.unsubscribe(eventName, subscriptionId)) {
|
|
282
|
+
count++;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.componentSubscriptions.delete(sliceId);
|
|
287
|
+
|
|
288
|
+
if (count > 0) {
|
|
289
|
+
slice.logger.logInfo('EventManager', `Limpiadas ${count} suscripción(es) de ${sliceId}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return count;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ============================================
|
|
296
|
+
// UTILIDADES
|
|
297
|
+
// ============================================
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Verificar si hay suscriptores para un evento
|
|
301
|
+
* @param {string} eventName - Nombre del evento
|
|
302
|
+
* @returns {boolean}
|
|
303
|
+
*/
|
|
304
|
+
hasSubscribers(eventName) {
|
|
305
|
+
return this.subscriptions.has(eventName) && this.subscriptions.get(eventName).size > 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Obtener cantidad de suscriptores para un evento
|
|
310
|
+
* @param {string} eventName - Nombre del evento
|
|
311
|
+
* @returns {number}
|
|
312
|
+
*/
|
|
313
|
+
subscriberCount(eventName) {
|
|
314
|
+
if (!this.subscriptions.has(eventName)) {
|
|
315
|
+
return 0;
|
|
316
|
+
}
|
|
317
|
+
return this.subscriptions.get(eventName).size;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Limpiar TODAS las suscripciones (usar con cuidado)
|
|
322
|
+
*/
|
|
323
|
+
clear() {
|
|
324
|
+
this.subscriptions.clear();
|
|
325
|
+
this.componentSubscriptions.clear();
|
|
326
|
+
this.idCounter = 0;
|
|
327
|
+
slice.logger.logInfo('EventManager', 'Todas las suscripciones eliminadas');
|
|
328
|
+
}
|
|
329
|
+
}
|