slicejs-web-framework 2.3.0 → 2.3.2

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.
@@ -7,6 +7,14 @@
7
7
  * - Auto-limpieza cuando componentes se destruyen
8
8
  * - API simple: subscribe, subscribeOnce, unsubscribe, emit
9
9
  */
10
+ /**
11
+ * @typedef {Object} EventManagerBind
12
+ * @property {(eventName: string, callback: Function) => string|null} subscribe
13
+ * @property {(eventName: string, callback: Function) => string|null} subscribeOnce
14
+ * @property {(eventName: string, data?: any) => void} emit
15
+ */
16
+
17
+
10
18
  export default class EventManager {
11
19
  constructor() {
12
20
  // Map<eventName, Map<subscriptionId, { callback, componentSliceId, once }>>
@@ -30,17 +38,17 @@ export default class EventManager {
30
38
  /**
31
39
  * Suscribirse a un evento
32
40
  * @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
41
+ * @param {(data?: any) => void} callback - Funcion a ejecutar cuando se emita el evento
42
+ * @param {{ component?: HTMLElement }} [options]
43
+ * @returns {string|null} subscriptionId - ID para desuscribirse
36
44
  *
37
45
  * @example
38
- * // Suscripción global
46
+ * // Suscripcion global
39
47
  * const id = slice.events.subscribe('user:login', (user) => {
40
48
  * console.log('Usuario:', user);
41
49
  * });
42
50
  *
43
- * // Suscripción con auto-cleanup
51
+ * // Suscripcion con auto-cleanup
44
52
  * slice.events.subscribe('user:login', (user) => {
45
53
  * this.actualizar(user);
46
54
  * }, { component: this });
@@ -78,9 +86,9 @@ export default class EventManager {
78
86
  /**
79
87
  * Suscribirse a un evento una sola vez
80
88
  * @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
89
+ * @param {(data?: any) => void} callback - Funcion a ejecutar
90
+ * @param {{ component?: HTMLElement }} [options]
91
+ * @returns {string|null} subscriptionId
84
92
  *
85
93
  * @example
86
94
  * slice.events.subscribeOnce('app:ready', () => {
@@ -117,12 +125,12 @@ export default class EventManager {
117
125
  /**
118
126
  * Desuscribirse de un evento
119
127
  * @param {string} eventName - Nombre del evento
120
- * @param {string} subscriptionId - ID de la suscripción
121
- * @returns {boolean} true si se eliminó correctamente
128
+ * @param {string} subscriptionId - ID de la suscripcion
129
+ * @returns {boolean} true si se elimino correctamente
122
130
  *
123
131
  * @example
124
132
  * const id = slice.events.subscribe('evento', callback);
125
- * // Después...
133
+ * // Despues...
126
134
  * slice.events.unsubscribe('evento', id);
127
135
  */
128
136
  unsubscribe(eventName, subscriptionId) {
@@ -147,7 +155,8 @@ export default class EventManager {
147
155
  /**
148
156
  * Emitir un evento
149
157
  * @param {string} eventName - Nombre del evento
150
- * @param {*} data - Datos a pasar a los callbacks
158
+ * @param {any} [data] - Datos a pasar a los callbacks
159
+ * @returns {void}
151
160
  *
152
161
  * @example
153
162
  * slice.events.emit('user:login', { id: 123, name: 'Juan' });
@@ -204,7 +213,7 @@ export default class EventManager {
204
213
  /**
205
214
  * Vincular el EventManager a un componente para auto-cleanup
206
215
  * @param {HTMLElement} component - Componente Slice con sliceId
207
- * @returns {Object} API vinculada al componente
216
+ * @returns {EventManagerBind|null} API vinculada al componente
208
217
  *
209
218
  * @example
210
219
  * class MiComponente extends HTMLElement {
@@ -0,0 +1,238 @@
1
+ /**
2
+ * EventManager debug panel.
3
+ */
4
+ export default class EventManagerDebugger extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+ this.isOpen = false;
8
+ this.filterText = '';
9
+ this.refreshInterval = null;
10
+ }
11
+
12
+ /**
13
+ * Initialize panel UI.
14
+ * @returns {Promise<void>}
15
+ */
16
+ async init() {
17
+ this.innerHTML = this.renderTemplate();
18
+ slice.stylesManager.registerComponentStyles('EventManagerDebugger', this.renderStyles());
19
+ this.cacheElements();
20
+ this.bindEvents();
21
+ this.renderList();
22
+ }
23
+
24
+ /**
25
+ * Toggle panel visibility.
26
+ * @returns {void}
27
+ */
28
+ toggle() {
29
+ this.isOpen = !this.isOpen;
30
+ this.container.classList.toggle('active', this.isOpen);
31
+ if (this.isOpen) {
32
+ this.renderList();
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Show panel.
38
+ * @returns {void}
39
+ */
40
+ open() {
41
+ this.isOpen = true;
42
+ this.container.classList.add('active');
43
+ this.renderList();
44
+ }
45
+
46
+ /**
47
+ * Hide panel.
48
+ * @returns {void}
49
+ */
50
+ close() {
51
+ this.isOpen = false;
52
+ this.container.classList.remove('active');
53
+ }
54
+
55
+ cacheElements() {
56
+ this.container = this.querySelector('#events-debugger');
57
+ this.list = this.querySelector('#events-list');
58
+ this.filterInput = this.querySelector('#events-filter');
59
+ this.countLabel = this.querySelector('#events-count');
60
+ this.refreshButton = this.querySelector('#events-refresh');
61
+ this.closeButton = this.querySelector('#events-close');
62
+ }
63
+
64
+ bindEvents() {
65
+ this.refreshButton.addEventListener('click', () => this.renderList());
66
+ this.closeButton.addEventListener('click', () => this.close());
67
+ this.filterInput.addEventListener('input', (event) => {
68
+ this.filterText = event.target.value.trim().toLowerCase();
69
+ this.renderList();
70
+ });
71
+ }
72
+
73
+ renderList() {
74
+ if (!slice?.events?.subscriptions) {
75
+ this.list.textContent = 'EventManager not available.';
76
+ this.countLabel.textContent = '0';
77
+ return;
78
+ }
79
+
80
+ const items = [];
81
+ slice.events.subscriptions.forEach((subs, eventName) => {
82
+ const count = subs.size;
83
+ if (this.filterText && !eventName.toLowerCase().includes(this.filterText)) {
84
+ return;
85
+ }
86
+ items.push({ eventName, count });
87
+ });
88
+
89
+ items.sort((a, b) => a.eventName.localeCompare(b.eventName));
90
+
91
+ this.countLabel.textContent = String(items.length);
92
+ this.list.innerHTML = items.length
93
+ ? items.map((item) => {
94
+ return `
95
+ <div class="event-row">
96
+ <div class="event-name">${item.eventName}</div>
97
+ <div class="event-count">${item.count}</div>
98
+ </div>
99
+ `;
100
+ }).join('')
101
+ : '<div class="empty">No events</div>';
102
+ }
103
+
104
+ renderTemplate() {
105
+ return `
106
+ <div id="events-debugger">
107
+ <div class="events-header">
108
+ <div class="title">Events</div>
109
+ <div class="actions">
110
+ <button id="events-refresh" class="btn">Refresh</button>
111
+ <button id="events-close" class="btn">Close</button>
112
+ </div>
113
+ </div>
114
+ <div class="events-toolbar">
115
+ <input id="events-filter" type="text" placeholder="Filter events" />
116
+ <div class="count">Total: <span id="events-count">0</span></div>
117
+ </div>
118
+ <div class="events-list" id="events-list"></div>
119
+ </div>
120
+ `;
121
+ }
122
+
123
+ renderStyles() {
124
+ return `
125
+ #events-debugger {
126
+ position: fixed;
127
+ bottom: 20px;
128
+ right: 20px;
129
+ width: min(360px, calc(100vw - 40px));
130
+ max-height: 60vh;
131
+ background: var(--primary-background-color);
132
+ border: 1px solid var(--medium-color);
133
+ border-radius: 12px;
134
+ box-shadow: 0 16px 32px rgba(0, 0, 0, 0.15);
135
+ display: none;
136
+ flex-direction: column;
137
+ z-index: 10001;
138
+ overflow: hidden;
139
+ }
140
+
141
+ #events-debugger.active {
142
+ display: flex;
143
+ }
144
+
145
+ #events-debugger * {
146
+ box-sizing: border-box;
147
+ }
148
+
149
+ .events-header {
150
+ display: flex;
151
+ justify-content: space-between;
152
+ align-items: center;
153
+ padding: 12px 14px;
154
+ background: var(--tertiary-background-color);
155
+ border-bottom: 1px solid var(--medium-color);
156
+ }
157
+
158
+ .events-header .title {
159
+ font-weight: 600;
160
+ color: var(--font-primary-color);
161
+ }
162
+
163
+ .events-header .actions {
164
+ display: flex;
165
+ gap: 8px;
166
+ }
167
+
168
+ .events-header .btn {
169
+ padding: 6px 10px;
170
+ border-radius: 6px;
171
+ border: 1px solid var(--medium-color);
172
+ background: var(--primary-background-color);
173
+ color: var(--font-primary-color);
174
+ cursor: pointer;
175
+ font-size: 12px;
176
+ }
177
+
178
+ .events-toolbar {
179
+ display: flex;
180
+ gap: 10px;
181
+ align-items: center;
182
+ padding: 10px 12px;
183
+ border-bottom: 1px solid var(--medium-color);
184
+ }
185
+
186
+ .events-toolbar input {
187
+ flex: 1;
188
+ min-width: 0;
189
+ padding: 6px 8px;
190
+ border-radius: 6px;
191
+ border: 1px solid var(--medium-color);
192
+ background: var(--primary-background-color);
193
+ color: var(--font-primary-color);
194
+ }
195
+
196
+ .events-list {
197
+ padding: 10px 12px;
198
+ overflow: auto;
199
+ display: flex;
200
+ flex-direction: column;
201
+ gap: 8px;
202
+ }
203
+
204
+ .event-row {
205
+ display: flex;
206
+ justify-content: space-between;
207
+ padding: 8px 10px;
208
+ background: var(--tertiary-background-color);
209
+ border-radius: 6px;
210
+ border: 1px solid var(--medium-color);
211
+ gap: 8px;
212
+ }
213
+
214
+ .event-name {
215
+ font-family: monospace;
216
+ font-size: 12px;
217
+ color: var(--font-primary-color);
218
+ overflow: hidden;
219
+ text-overflow: ellipsis;
220
+ white-space: nowrap;
221
+ }
222
+
223
+ .event-count {
224
+ font-weight: 600;
225
+ color: var(--primary-color);
226
+ }
227
+
228
+ .empty {
229
+ color: var(--font-secondary-color);
230
+ font-size: 12px;
231
+ text-align: center;
232
+ padding: 12px 0;
233
+ }
234
+ `;
235
+ }
236
+ }
237
+
238
+ customElements.define('slice-eventmanager-debugger', EventManagerDebugger);
@@ -1,104 +1,146 @@
1
- import Log from './Log.js';
2
-
3
- export default class Logger {
4
- constructor() {
5
- this.logs = [];
6
- this.logEnabled = slice.loggerConfig.enabled;
7
- this.showLogsConfig = slice.loggerConfig.showLogs;
8
- console.log(slice.loggerConfig)
9
-
10
- this.showLog = function showLog(log) {
11
- if (!this.showLogsConfig) return;
12
-
13
- const logType = log.logType;
14
-
15
- Object.keys(this.showLogsConfig).forEach((logConfig) => {
16
- if (this.showLogsConfig[logConfig][logType] === true) {
17
- switch (logConfig) {
18
- case 'console':
19
- switch (logType) {
20
- case logTypes.ERROR:
21
- console.error(
22
- `\x1b[31mERROR\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message} - ${log.error}`
23
- );
24
- break;
25
- case logTypes.WARNING:
26
- console.warn(
27
- `\x1b[33m⚠ WARNING\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
28
- );
29
- break;
30
- case logTypes.INFO:
31
- console.log(
32
- `\x1b[32m✔ INFO\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
33
- );
34
- break;
35
- default:
36
- console.log(
37
- `\x1b[37mUNKNOWN\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
38
- );
39
- }
40
- break;
41
- }
42
- }
43
- });
44
- };
45
- }
46
-
47
- createLog(logType, componentSliceId, message, error = null) {
48
- if (!this.logEnabled) return;
49
- let componentName;
50
-
51
- try {
52
- componentName = slice.controller.activeComponents.get(componentSliceId).constructor.name;
53
- } catch (error) {
54
- componentName = componentSliceId;
55
- }
56
-
57
- let componentCategory = slice.controller.getComponentCategory(componentName);
58
- if (componentSliceId === 'Slice' || componentSliceId === 'ThemeManager') componentCategory = 'Structural';
59
- const log = new Log(logType, componentCategory, componentSliceId, message, error);
60
- this.logs.push(log);
61
- this.showLog(log);
62
- }
63
-
64
- logError(componentSliceId, message, error) {
65
- this.createLog(logTypes.ERROR, componentSliceId, message, error);
66
- }
67
-
68
- logWarning(componentSliceId, message) {
69
- this.createLog(logTypes.WARNING, componentSliceId, message);
70
- }
71
-
72
- logInfo(componentSliceId, message) {
73
- this.createLog(logTypes.INFO, componentSliceId, message);
74
- }
75
-
76
- getLogs() {
77
- return this.logs;
78
- }
79
-
80
- clearLogs() {
81
- this.logs = [];
82
- }
83
-
84
- getLogsByLogType(type) {
85
- return this.logs.filter((log) => log.logType === type);
86
- }
87
-
88
- getLogsByComponentCategory(componentCategory) {
89
- return this.logs.filter((log) => log.componentCategory === componentCategory);
90
- }
91
-
92
- getLogsByComponent(componentSliceId) {
93
- return this.logs.filter((log) => log.componentSliceId === componentSliceId);
94
- }
95
- }
96
-
97
- // En esta misma idea, se tiene que tomar en cuenta que el componente de ToastAlert será un toastProvider y que solo debe
98
- // haber un toastProvider en la página, por lo que se debe implementar un Singleton para el ToastProvider
99
-
100
- const logTypes = {
101
- ERROR: 'error',
102
- WARNING: 'warning',
103
- INFO: 'info',
104
- };
1
+ import Log from './Log.js';
2
+
3
+ export default class Logger {
4
+ constructor() {
5
+ this.logs = [];
6
+ this.logEnabled = slice.loggerConfig.enabled;
7
+ this.showLogsConfig = slice.loggerConfig.showLogs;
8
+ console.log(slice.loggerConfig)
9
+
10
+ this.showLog = function showLog(log) {
11
+ if (!this.showLogsConfig) return;
12
+
13
+ const logType = log.logType;
14
+
15
+ Object.keys(this.showLogsConfig).forEach((logConfig) => {
16
+ if (this.showLogsConfig[logConfig][logType] === true) {
17
+ switch (logConfig) {
18
+ case 'console':
19
+ switch (logType) {
20
+ case logTypes.ERROR:
21
+ console.error(
22
+ `\x1b[31mERROR\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message} - ${log.error}`
23
+ );
24
+ break;
25
+ case logTypes.WARNING:
26
+ console.warn(
27
+ `\x1b[33m⚠ WARNING\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
28
+ );
29
+ break;
30
+ case logTypes.INFO:
31
+ console.log(
32
+ `\x1b[32m✔ INFO\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
33
+ );
34
+ break;
35
+ default:
36
+ console.log(
37
+ `\x1b[37mUNKNOWN\x1b[0m - ${log.componentCategory} - ${log.componentSliceId} - ${log.message}`
38
+ );
39
+ }
40
+ break;
41
+ }
42
+ }
43
+ });
44
+ };
45
+ }
46
+
47
+ createLog(logType, componentSliceId, message, error = null) {
48
+ if (!this.logEnabled) return;
49
+ let componentName;
50
+
51
+ try {
52
+ componentName = slice.controller.activeComponents.get(componentSliceId).constructor.name;
53
+ } catch (error) {
54
+ componentName = componentSliceId;
55
+ }
56
+
57
+ let componentCategory = slice.controller.getComponentCategory(componentName);
58
+ if (componentSliceId === 'Slice' || componentSliceId === 'ThemeManager') componentCategory = 'Structural';
59
+ const log = new Log(logType, componentCategory, componentSliceId, message, error);
60
+ this.logs.push(log);
61
+ this.showLog(log);
62
+ }
63
+
64
+ /**
65
+ * Log an error message.
66
+ * @param {string} componentSliceId
67
+ * @param {string} message
68
+ * @param {any} [error]
69
+ * @returns {void}
70
+ */
71
+ logError(componentSliceId, message, error) {
72
+ this.createLog(logTypes.ERROR, componentSliceId, message, error);
73
+ }
74
+
75
+ /**
76
+ * Log a warning message.
77
+ * @param {string} componentSliceId
78
+ * @param {string} message
79
+ * @returns {void}
80
+ */
81
+ logWarning(componentSliceId, message) {
82
+ this.createLog(logTypes.WARNING, componentSliceId, message);
83
+ }
84
+
85
+ /**
86
+ * Log an info message.
87
+ * @param {string} componentSliceId
88
+ * @param {string} message
89
+ * @returns {void}
90
+ */
91
+ logInfo(componentSliceId, message) {
92
+ this.createLog(logTypes.INFO, componentSliceId, message);
93
+ }
94
+
95
+ /**
96
+ * Get all logs.
97
+ * @returns {Array}
98
+ */
99
+ getLogs() {
100
+ return this.logs;
101
+ }
102
+
103
+ /**
104
+ * Clear all logs.
105
+ * @returns {void}
106
+ */
107
+ clearLogs() {
108
+ this.logs = [];
109
+ }
110
+
111
+ /**
112
+ * Filter logs by type.
113
+ * @param {string} type
114
+ * @returns {Array}
115
+ */
116
+ getLogsByLogType(type) {
117
+ return this.logs.filter((log) => log.logType === type);
118
+ }
119
+
120
+ /**
121
+ * Filter logs by component category.
122
+ * @param {string} componentCategory
123
+ * @returns {Array}
124
+ */
125
+ getLogsByComponentCategory(componentCategory) {
126
+ return this.logs.filter((log) => log.componentCategory === componentCategory);
127
+ }
128
+
129
+ /**
130
+ * Filter logs by component sliceId.
131
+ * @param {string} componentSliceId
132
+ * @returns {Array}
133
+ */
134
+ getLogsByComponent(componentSliceId) {
135
+ return this.logs.filter((log) => log.componentSliceId === componentSliceId);
136
+ }
137
+ }
138
+
139
+ // En esta misma idea, se tiene que tomar en cuenta que el componente de ToastAlert será un toastProvider y que solo debe
140
+ // haber un toastProvider en la página, por lo que se debe implementar un Singleton para el ToastProvider
141
+
142
+ const logTypes = {
143
+ ERROR: 'error',
144
+ WARNING: 'warning',
145
+ INFO: 'info',
146
+ };