slicejs-web-framework 3.3.7 → 3.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.
@@ -6,7 +6,10 @@ export default class EventManagerDebugger extends HTMLElement {
6
6
  super();
7
7
  this.isOpen = false;
8
8
  this.filterText = '';
9
+ this.activeTab = 'subscribers';
9
10
  this.refreshInterval = null;
11
+ this._autoRefreshTimer = null;
12
+ this._lastHistoryLength = 0;
10
13
  }
11
14
 
12
15
  /**
@@ -41,6 +44,11 @@ export default class EventManagerDebugger extends HTMLElement {
41
44
  open() {
42
45
  this.isOpen = true;
43
46
  this.container.classList.add('active');
47
+ this.activeTab = 'subscribers';
48
+ if (slice.events) {
49
+ slice.events.startRecording();
50
+ }
51
+ this._startAutoRefresh();
44
52
  this.renderList();
45
53
  }
46
54
 
@@ -51,6 +59,29 @@ export default class EventManagerDebugger extends HTMLElement {
51
59
  close() {
52
60
  this.isOpen = false;
53
61
  this.container.classList.remove('active');
62
+ this._stopAutoRefresh();
63
+ if (slice.events) {
64
+ slice.events.stopRecording();
65
+ }
66
+ }
67
+
68
+ _startAutoRefresh() {
69
+ this._stopAutoRefresh();
70
+ this._autoRefreshTimer = setInterval(() => {
71
+ if (!this.isOpen) return;
72
+ if (this.activeTab === 'history') {
73
+ this.renderHistory();
74
+ } else {
75
+ this.renderList();
76
+ }
77
+ }, 1500);
78
+ }
79
+
80
+ _stopAutoRefresh() {
81
+ if (this._autoRefreshTimer) {
82
+ clearInterval(this._autoRefreshTimer);
83
+ this._autoRefreshTimer = null;
84
+ }
54
85
  }
55
86
 
56
87
  cacheElements() {
@@ -61,15 +92,35 @@ export default class EventManagerDebugger extends HTMLElement {
61
92
  this.countLabel = this.querySelector('#events-count');
62
93
  this.refreshButton = this.querySelector('#events-refresh');
63
94
  this.closeButton = this.querySelector('#events-close');
95
+ this.tabSubs = this.querySelector('#tab-subscribers');
96
+ this.tabHistory = this.querySelector('#tab-history');
64
97
  }
65
98
 
66
99
  bindEvents() {
67
- this.refreshButton.addEventListener('click', () => this.renderList());
100
+ this.refreshButton.addEventListener('click', () => {
101
+ if (this.activeTab === 'history') this.renderHistory();
102
+ else this.renderList();
103
+ });
68
104
  this.closeButton.addEventListener('click', () => this.close());
69
105
  this.filterInput.addEventListener('input', (event) => {
70
106
  this.filterText = event.target.value.trim().toLowerCase();
107
+ if (this.activeTab === 'history') this.renderHistory();
108
+ else this.renderList();
109
+ });
110
+ this.tabSubs.addEventListener('click', () => {
111
+ this.activeTab = 'subscribers';
112
+ this.tabSubs.classList.add('active');
113
+ this.tabHistory.classList.remove('active');
114
+ this.filterInput.placeholder = 'filter events…';
71
115
  this.renderList();
72
116
  });
117
+ this.tabHistory.addEventListener('click', () => {
118
+ this.activeTab = 'history';
119
+ this.tabHistory.classList.add('active');
120
+ this.tabSubs.classList.remove('active');
121
+ this.filterInput.placeholder = 'filter history…';
122
+ this.renderHistory();
123
+ });
73
124
  }
74
125
 
75
126
  makeDraggable() {
@@ -117,6 +168,7 @@ export default class EventManagerDebugger extends HTMLElement {
117
168
 
118
169
  const items = [];
119
170
  slice.events.subscriptions.forEach((subs, eventName) => {
171
+ const emitCount = slice.events.emitCounts?.get(eventName) || 0;
120
172
  const entries = Array.from(subs.entries()).map(([id, sub]) => {
121
173
  const componentSliceId = sub.componentSliceId || null;
122
174
  const component = componentSliceId ? slice.controller.getComponent(componentSliceId) : null;
@@ -133,7 +185,7 @@ export default class EventManagerDebugger extends HTMLElement {
133
185
  return;
134
186
  }
135
187
 
136
- items.push({ eventName, count: subs.size, entries });
188
+ items.push({ eventName, count: subs.size, emitCount, entries });
137
189
  });
138
190
 
139
191
  items.sort((a, b) => a.eventName.localeCompare(b.eventName));
@@ -141,32 +193,76 @@ export default class EventManagerDebugger extends HTMLElement {
141
193
  this.countLabel.textContent = String(items.length);
142
194
  this.list.innerHTML = items.length
143
195
  ? items.map((item) => {
144
- const details = item.entries.map((entry) => {
145
- const label = entry.componentName
146
- ? `${entry.componentName} (${entry.componentSliceId})`
147
- : entry.componentSliceId || 'Global';
148
- const onceBadge = entry.once ? '<span class="badge">once</span>' : '';
149
- return `
150
- <div class="subscriber-row">
151
- <div class="subscriber-name">${label}</div>
152
- <div class="subscriber-meta">${entry.id}${onceBadge}</div>
153
- </div>
154
- `;
155
- }).join('');
156
-
157
- return `
158
- <details class="event-row">
159
- <summary>
160
- <div class="event-name">${item.eventName}</div>
161
- <div class="event-count">${item.count}</div>
162
- </summary>
163
- <div class="subscriber-list">
164
- ${details || '<div class="empty">No subscribers</div>'}
165
- </div>
166
- </details>
167
- `;
168
- }).join('')
169
- : '<div class="empty">No events</div>';
196
+ const details = item.entries.map((entry) => {
197
+ const label = entry.componentName
198
+ ? `${entry.componentName} (${entry.componentSliceId})`
199
+ : entry.componentSliceId || 'Global';
200
+ const onceBadge = entry.once ? '<span class="badge">once</span>' : '';
201
+ return `
202
+ <div class="subscriber-row">
203
+ <div class="subscriber-name">${label}</div>
204
+ <div class="subscriber-meta">${entry.id}${onceBadge}</div>
205
+ </div>
206
+ `;
207
+ }).join('');
208
+
209
+ return `
210
+ <details class="event-row">
211
+ <summary>
212
+ <div class="event-name">${item.eventName}</div>
213
+ <div class="event-metrics">
214
+ <span class="emit-count" title="Emits this session">⚡${item.emitCount}</span>
215
+ <span class="event-count">${item.count}</span>
216
+ </div>
217
+ </summary>
218
+ <div class="subscriber-list">
219
+ ${details || '<div class="empty">No subscribers</div>'}
220
+ </div>
221
+ </details>
222
+ `;
223
+ }).join('')
224
+ : '<div class="empty">No events</div>';
225
+ }
226
+
227
+ renderHistory() {
228
+ if (!slice?.events?.emitHistory) {
229
+ this.list.textContent = 'EventManager not available.';
230
+ this.countLabel.textContent = '0';
231
+ return;
232
+ }
233
+
234
+ const history = slice.events.emitHistory;
235
+ const filtered = this.filterText
236
+ ? history.filter(e => e.eventName.toLowerCase().includes(this.filterText))
237
+ : history;
238
+
239
+ this.countLabel.textContent = String(filtered.length);
240
+ if (!filtered.length) {
241
+ this.list.innerHTML = '<div class="empty">No emits recorded yet</div>';
242
+ return;
243
+ }
244
+
245
+ const now = Date.now();
246
+ this.list.innerHTML = [...filtered].reverse().map((entry) => {
247
+ const diff = now - entry.timestamp;
248
+ const timeStr = diff < 1000 ? 'now'
249
+ : diff < 60000 ? `${Math.floor(diff / 1000)}s ago`
250
+ : `${Math.floor(diff / 60000)}m ago`;
251
+ return `
252
+ <div class="history-row">
253
+ <div class="history-event">${this.escapeHtml(entry.eventName)}</div>
254
+ <div class="history-time">${timeStr}</div>
255
+ </div>
256
+ `;
257
+ }).join('');
258
+ }
259
+
260
+ escapeHtml(value) {
261
+ return String(value)
262
+ .replace(/&/g, '&amp;')
263
+ .replace(/</g, '&lt;')
264
+ .replace(/>/g, '&gt;')
265
+ .replace(/"/g, '&quot;');
170
266
  }
171
267
 
172
268
  renderTemplate() {
@@ -183,6 +279,10 @@ export default class EventManagerDebugger extends HTMLElement {
183
279
  <button id="events-close" class="btn" title="Close" aria-label="Close">✕</button>
184
280
  </div>
185
281
  </div>
282
+ <div class="events-tabs">
283
+ <button id="tab-subscribers" class="tab-btn active">Subscribers</button>
284
+ <button id="tab-history" class="tab-btn">History</button>
285
+ </div>
186
286
  <div class="events-toolbar">
187
287
  <input id="events-filter" type="text" placeholder="filter events…" autocomplete="off" spellcheck="false" />
188
288
  <div class="count"><span id="events-count">0</span></div>
@@ -195,18 +295,22 @@ export default class EventManagerDebugger extends HTMLElement {
195
295
  renderStyles() {
196
296
  return `
197
297
  /* Slice Instruments — events console. All selectors scoped to the
198
- <slice-eventmanager-debugger> tag so nothing clashes with app styles. */
298
+ <slice-eventmanager-debugger> tag so nothing clashes with app styles.
299
+ Every --si-* token reads the matching framework theme variable from
300
+ :root, falling back to the original hardcoded value if absent. */
199
301
  slice-eventmanager-debugger {
200
- --si-accent: var(--primary-color, #6ee7ff);
201
- --si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
202
- --si-surface: rgba(17, 19, 28, 0.86);
203
- --si-raised: rgba(255, 255, 255, 0.035);
204
- --si-raised-2: rgba(255, 255, 255, 0.06);
205
- --si-border: rgba(255, 255, 255, 0.09);
206
- --si-text: #e8eaf2;
207
- --si-dim: #888fa6;
208
- --si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
209
- }
302
+ --si-accent: var(--primary-color, #6ee7ff);
303
+ --si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
304
+ --si-surface: var(--primary-background-color, rgba(17, 19, 28, 0.86));
305
+ --si-raised: var(--secondary-background-color, rgba(255, 255, 255, 0.035));
306
+ --si-raised-2: var(--tertiary-background-color, rgba(255, 255, 255, 0.06));
307
+ --si-border: var(--medium-color, rgba(255, 255, 255, 0.09));
308
+ --si-text: var(--font-primary-color, #e8eaf2);
309
+ --si-dim: var(--font-secondary-color, #888fa6);
310
+ --si-danger: var(--danger-color, #ff6b6b);
311
+ --si-success: var(--success-color, #46d39a);
312
+ --si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
313
+ }
210
314
 
211
315
  slice-eventmanager-debugger #events-debugger {
212
316
  position: fixed;
@@ -311,6 +415,31 @@ slice-eventmanager-debugger .btn:hover {
311
415
  slice-eventmanager-debugger .btn:active { transform: scale(0.92); }
312
416
  slice-eventmanager-debugger #events-refresh:hover { color: var(--si-accent); }
313
417
 
418
+ slice-eventmanager-debugger .events-tabs {
419
+ display: flex;
420
+ gap: 0;
421
+ padding: 0 12px;
422
+ border-bottom: 1px solid var(--si-border);
423
+ background: var(--si-raised);
424
+ }
425
+ slice-eventmanager-debugger .tab-btn {
426
+ all: unset;
427
+ cursor: pointer;
428
+ font-size: 10px;
429
+ font-weight: 600;
430
+ letter-spacing: 0.06em;
431
+ text-transform: uppercase;
432
+ padding: 7px 12px;
433
+ color: var(--si-dim);
434
+ border-bottom: 2px solid transparent;
435
+ transition: color 0.15s ease, border-color 0.15s ease;
436
+ }
437
+ slice-eventmanager-debugger .tab-btn:hover { color: var(--si-text); }
438
+ slice-eventmanager-debugger .tab-btn.active {
439
+ color: var(--si-accent);
440
+ border-bottom-color: var(--si-accent);
441
+ }
442
+
314
443
  slice-eventmanager-debugger .events-toolbar {
315
444
  display: flex;
316
445
  gap: 10px;
@@ -397,6 +526,21 @@ slice-eventmanager-debugger .event-name {
397
526
  white-space: nowrap;
398
527
  }
399
528
 
529
+ slice-eventmanager-debugger .event-metrics {
530
+ display: flex;
531
+ align-items: center;
532
+ gap: 6px;
533
+ }
534
+ slice-eventmanager-debugger .emit-count {
535
+ font-weight: 600;
536
+ font-size: 10px;
537
+ color: var(--si-success);
538
+ background: rgba(70, 211, 154, 0.12);
539
+ border: 1px solid rgba(70, 211, 154, 0.25);
540
+ padding: 1px 6px;
541
+ border-radius: 999px;
542
+ white-space: nowrap;
543
+ }
400
544
  slice-eventmanager-debugger .event-count {
401
545
  font-weight: 600;
402
546
  font-size: 11px;
@@ -408,6 +552,33 @@ slice-eventmanager-debugger .event-count {
408
552
  min-width: 22px;
409
553
  text-align: center;
410
554
  }
555
+ slice-eventmanager-debugger .history-row {
556
+ display: flex;
557
+ justify-content: space-between;
558
+ align-items: center;
559
+ gap: 10px;
560
+ padding: 6px 11px;
561
+ background: var(--si-raised);
562
+ border-radius: 7px;
563
+ border: 1px solid var(--si-border);
564
+ border-left: 2px solid transparent;
565
+ transition: border-color 0.15s ease;
566
+ }
567
+ slice-eventmanager-debugger .history-row:hover { border-left-color: var(--si-accent); }
568
+ slice-eventmanager-debugger .history-event {
569
+ font-family: var(--si-mono);
570
+ font-size: 11.5px;
571
+ color: var(--si-text);
572
+ overflow: hidden;
573
+ text-overflow: ellipsis;
574
+ white-space: nowrap;
575
+ }
576
+ slice-eventmanager-debugger .history-time {
577
+ font-size: 10px;
578
+ color: var(--si-dim);
579
+ white-space: nowrap;
580
+ flex-shrink: 0;
581
+ }
411
582
 
412
583
  slice-eventmanager-debugger .subscriber-list {
413
584
  margin-top: 9px;