bee-ladybug 0.5.2 → 0.5.3
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 +2 -2
- package/package.json +1 -1
- package/src/adapters/CSSAdapter.js +99 -38
- package/src/core/BeeLadybugCore.js +2 -2
- package/src/core/UIOverlay.js +166 -33
- package/src/index.d.ts +2 -0
package/README.MD
CHANGED
|
@@ -195,13 +195,13 @@ css.attach();
|
|
|
195
195
|
|
|
196
196
|
`F6` (pulsante **F6 CSS** accanto a F3/F4) accende la caccia. Sull'elemento sotto il puntatore l'overlay elenca:
|
|
197
197
|
|
|
198
|
-
- regole `!important` che gli corrispondono
|
|
198
|
+
- regole `!important` che gli corrispondono. Un foglio cross-origin è una nota grigia, una volta sola: `unreadable sheet: <href>`
|
|
199
199
|
- overflow del viewport e margini negativi
|
|
200
200
|
- testo trasparente, `opacity: 0`, o colore uguale allo sfondo
|
|
201
201
|
- chi intercetta il click al centro, inclusi `::before` / `::after` fissi su `html` / `body`
|
|
202
202
|
- `<img>` con rapporto del file diverso di oltre il 5% dal riquadro disegnato (`getBoundingClientRect`), solo se `object-fit` calcolato è `fill` (il default)
|
|
203
203
|
|
|
204
|
-
`S` con la hunt accesa percorre la pagina. L'overflow resta solo sull'elemento che lo causa: un antenato si salta se un discendente sfora della stessa quantità (±2px), oppure se la larghezza del box ci sta nel viewport e a uscire è un figlio. Righe uguali (stesso selettore e stesso problema) diventano una sola, con il conteggio (`article overflows viewport ×4`). L'ordine è: causa di overflow, click bloccato, testo invisibile, immagine distorta, `!important`. Un click sulla riga disegna il contorno rosso. `P` ferma il pannello sull'elemento corrente,
|
|
204
|
+
`S` con la hunt accesa percorre la pagina. L'overflow resta solo sull'elemento che lo causa: un antenato si salta se un discendente sfora della stessa quantità (±2px), oppure se la larghezza del box ci sta nel viewport e a uscire è un figlio. Righe uguali (stesso selettore e stesso problema) diventano una sola, con il conteggio (`article overflows viewport ×4`). L'ordine è: causa di overflow, click bloccato, testo invisibile, immagine distorta, `!important`. Un click sulla riga disegna il contorno rosso. `P` ferma il pannello sull'elemento corrente. **Issues** raggruppa per causa: `body::after blocks clicks on 10 elements` è una riga rossa, e il click la apre sull'elenco degli elementi. In cima alla sezione, la legenda dice rosso = bug e grigio = info. Un foglio illeggibile è una riga grigia, una per foglio, e non entra tra i bug. L'overlay resta alto al massimo metà schermo, scorre al suo interno, e le sezioni Status, Issues, Inspect ed Events si chiudono. In console un elemento pulito non viene scritto, e con la hunt accesa restano fuori anche `hover` e `mutate attr` del DOM, a meno che `F3` SLOW sia acceso. Ogni problema CSS viene scritto una sola volta per elemento ogni 5 secondi. I fogli di stile restano in cache finché un `MutationObserver` non vede un cambio a `<style>` o `<link>`. Hover è limitato a un frame.
|
|
205
205
|
|
|
206
206
|
Demo: `examples/css-hunt.html`.
|
|
207
207
|
|
package/package.json
CHANGED
|
@@ -42,7 +42,6 @@ export class CSSAdapter {
|
|
|
42
42
|
this.#attached = false;
|
|
43
43
|
this.#active = false;
|
|
44
44
|
this.#rules = [];
|
|
45
|
-
this.#unreadable = [];
|
|
46
45
|
this.#sheetsDirty = true;
|
|
47
46
|
this.#x = 0;
|
|
48
47
|
this.#y = 0;
|
|
@@ -51,6 +50,8 @@ export class CSSAdapter {
|
|
|
51
50
|
this.#pinned = null;
|
|
52
51
|
this.#panelLock = false;
|
|
53
52
|
this.#issues = [];
|
|
53
|
+
this.#notes = [];
|
|
54
|
+
this.#seenSheets = new Set();
|
|
54
55
|
this.#issueLog = new Map();
|
|
55
56
|
this.#hoverBox = null;
|
|
56
57
|
this.#pinBox = null;
|
|
@@ -72,7 +73,6 @@ export class CSSAdapter {
|
|
|
72
73
|
#attached;
|
|
73
74
|
#active;
|
|
74
75
|
#rules;
|
|
75
|
-
#unreadable;
|
|
76
76
|
#sheetsDirty;
|
|
77
77
|
#x;
|
|
78
78
|
#y;
|
|
@@ -81,6 +81,8 @@ export class CSSAdapter {
|
|
|
81
81
|
#pinned;
|
|
82
82
|
#panelLock;
|
|
83
83
|
#issues;
|
|
84
|
+
#notes;
|
|
85
|
+
#seenSheets;
|
|
84
86
|
#issueLog;
|
|
85
87
|
#hoverBox;
|
|
86
88
|
#pinBox;
|
|
@@ -159,7 +161,11 @@ export class CSSAdapter {
|
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
163
|
const groups = groupIssues(rows);
|
|
162
|
-
for (let i =
|
|
164
|
+
for (let i = 0; i < rows.length; i++) {
|
|
165
|
+
const row = rows[i];
|
|
166
|
+
this.#rememberCause(row.el, row.selector, row.kind);
|
|
167
|
+
}
|
|
168
|
+
this.#publishIssues();
|
|
163
169
|
const shown = groups.slice(0, this.maxScan);
|
|
164
170
|
const items = shown.map((group) => ({
|
|
165
171
|
level: group.level,
|
|
@@ -228,6 +234,8 @@ export class CSSAdapter {
|
|
|
228
234
|
this.#pinned = null;
|
|
229
235
|
this.#panelLock = false;
|
|
230
236
|
this.#issues = [];
|
|
237
|
+
this.#notes = [];
|
|
238
|
+
this.#seenSheets.clear();
|
|
231
239
|
this.#issueLog.clear();
|
|
232
240
|
this.#unmountBoxes();
|
|
233
241
|
this.core?.publishDetail([]);
|
|
@@ -264,13 +272,8 @@ export class CSSAdapter {
|
|
|
264
272
|
this.#sheetsDirty = false;
|
|
265
273
|
const built = collectImportantRules();
|
|
266
274
|
this.#rules = built.rules;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.core?.sendData('warn', {
|
|
270
|
-
source: this.source,
|
|
271
|
-
message: `unreadable sheet: ${built.unreadable[i]}`
|
|
272
|
-
});
|
|
273
|
-
}
|
|
275
|
+
for (let i = 0; i < built.unreadable.length; i++) this.#rememberSheet(built.unreadable[i]);
|
|
276
|
+
this.#publishIssues();
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
#inspect(x, y) {
|
|
@@ -290,13 +293,6 @@ export class CSSAdapter {
|
|
|
290
293
|
pushInvisible(el, findings);
|
|
291
294
|
pushBlockers(el, findings);
|
|
292
295
|
pushImage(el, findings);
|
|
293
|
-
for (let i = 0; i < this.#unreadable.length; i++) {
|
|
294
|
-
findings.push({
|
|
295
|
-
level: 'warn',
|
|
296
|
-
text: `unreadable sheet: ${this.#unreadable[i]}`,
|
|
297
|
-
score: 0
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
296
|
const path = cssPath(el);
|
|
301
297
|
this.core?.sendData('state', { source: this.source, key: 'css.target', value: path });
|
|
302
298
|
this.core?.sendData('state', {
|
|
@@ -344,7 +340,7 @@ export class CSSAdapter {
|
|
|
344
340
|
for (let i = 0; i < findings.length; i++) {
|
|
345
341
|
const hit = findings[i];
|
|
346
342
|
const text = hit.text || hit.kind || '';
|
|
347
|
-
if (!text) continue;
|
|
343
|
+
if (!text || text.startsWith('unreadable sheet:')) continue;
|
|
348
344
|
if (!rememberIssue(this.#issueLog, `${path}\0${text}`, now, this.issueWindow)) continue;
|
|
349
345
|
const level = hit.level === 'error' ? 'error' : 'warn';
|
|
350
346
|
this.core?.sendData(level, {
|
|
@@ -355,34 +351,50 @@ export class CSSAdapter {
|
|
|
355
351
|
}
|
|
356
352
|
|
|
357
353
|
#stickFindings(el, path, findings) {
|
|
354
|
+
let changed = false;
|
|
358
355
|
for (let i = 0; i < findings.length; i++) {
|
|
359
356
|
const hit = findings[i];
|
|
360
|
-
if (!isStickyKind(hit.kind)) continue;
|
|
361
357
|
if (hit.kind === 'overflows viewport' && !elementCausesOverflow(el)) continue;
|
|
362
|
-
|
|
363
|
-
text: `${path} ${hit.kind}`,
|
|
364
|
-
el
|
|
365
|
-
}, this.maxIssues);
|
|
358
|
+
if (this.#rememberCause(el, path, hit.kind)) changed = true;
|
|
366
359
|
}
|
|
367
|
-
this.#publishIssues();
|
|
360
|
+
if (changed) this.#publishIssues();
|
|
368
361
|
}
|
|
369
362
|
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
this.#issues =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
363
|
+
#rememberCause(el, path, kind) {
|
|
364
|
+
const next = addCause(this.#issues, { kind, path, el }, this.maxIssues);
|
|
365
|
+
if (next === this.#issues) return false;
|
|
366
|
+
this.#issues = next;
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#rememberSheet(href) {
|
|
371
|
+
const label = String(href || '');
|
|
372
|
+
if (!label || this.#seenSheets.has(label)) return;
|
|
373
|
+
this.#seenSheets.add(label);
|
|
374
|
+
this.#notes.push({ text: `unreadable sheet: ${label}` });
|
|
375
|
+
this.core?.sendData('log', {
|
|
376
|
+
source: this.source,
|
|
377
|
+
level: 'info',
|
|
378
|
+
message: `unreadable sheet: ${label}`
|
|
379
|
+
});
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
#publishIssues() {
|
|
381
|
-
this
|
|
383
|
+
const bugs = this.#issues.map((entry) => ({
|
|
382
384
|
level: 'error',
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
key: entry.key,
|
|
386
|
+
text: formatCause(entry),
|
|
387
|
+
targets: entry.targets.map((target) => ({
|
|
388
|
+
text: target.text,
|
|
389
|
+
onSelect: () => this.#pin(target.el)
|
|
390
|
+
}))
|
|
391
|
+
}));
|
|
392
|
+
const notes = this.#notes.map((note) => ({
|
|
393
|
+
level: 'info',
|
|
394
|
+
key: note.text,
|
|
395
|
+
text: note.text
|
|
396
|
+
}));
|
|
397
|
+
this.core?.publishIssues(bugs.concat(notes));
|
|
386
398
|
}
|
|
387
399
|
|
|
388
400
|
#pin(el) {
|
|
@@ -715,8 +727,57 @@ export function pushStickyIssue(list, entry, max = 10) {
|
|
|
715
727
|
return next.slice(0, max);
|
|
716
728
|
}
|
|
717
729
|
|
|
718
|
-
function
|
|
719
|
-
|
|
730
|
+
export function describeCause(kind, path) {
|
|
731
|
+
if (kind === 'overflows viewport') {
|
|
732
|
+
return { key: `overflow\0${path}`, type: 'overflow', cause: path };
|
|
733
|
+
}
|
|
734
|
+
const prefix = 'click blocked by ';
|
|
735
|
+
if (typeof kind === 'string' && kind.startsWith(prefix)) {
|
|
736
|
+
const cause = kind.slice(prefix.length);
|
|
737
|
+
return { key: `block\0${cause}`, type: 'block', cause };
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* One row per cause. A blocker that covers many nodes stays a single row.
|
|
744
|
+
* @param {Array<{ key: string, type: string, cause: string, targets: Array<{ text: string, el?: Element }> }>} list
|
|
745
|
+
* @param {{ kind?: string, path: string, el?: Element }} entry
|
|
746
|
+
*/
|
|
747
|
+
export function addCause(list, entry, max = 10) {
|
|
748
|
+
const described = describeCause(entry.kind, entry.path);
|
|
749
|
+
if (!described) return list;
|
|
750
|
+
const target = { text: entry.path, el: entry.el };
|
|
751
|
+
const rest = [];
|
|
752
|
+
let prev = null;
|
|
753
|
+
for (let i = 0; i < list.length; i++) {
|
|
754
|
+
if (list[i].key === described.key) prev = list[i];
|
|
755
|
+
else rest.push(list[i]);
|
|
756
|
+
}
|
|
757
|
+
const targets = prev ? prev.targets.filter((item) => !sameTarget(item, target)) : [];
|
|
758
|
+
targets.unshift(target);
|
|
759
|
+
rest.unshift({
|
|
760
|
+
key: described.key,
|
|
761
|
+
type: described.type,
|
|
762
|
+
cause: described.cause,
|
|
763
|
+
targets
|
|
764
|
+
});
|
|
765
|
+
return rest.slice(0, max);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
export function formatCause(entry) {
|
|
769
|
+
const count = entry.targets.length;
|
|
770
|
+
if (entry.type === 'block') {
|
|
771
|
+
const noun = count === 1 ? 'element' : 'elements';
|
|
772
|
+
return `${entry.cause} blocks clicks on ${count} ${noun}`;
|
|
773
|
+
}
|
|
774
|
+
if (count <= 1) return `${entry.cause} overflows viewport`;
|
|
775
|
+
return `${entry.cause} overflows viewport · ${count} elements`;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function sameTarget(a, b) {
|
|
779
|
+
if (a.el && b.el) return a.el === b.el;
|
|
780
|
+
return a.text === b.text;
|
|
720
781
|
}
|
|
721
782
|
|
|
722
783
|
function elementCausesOverflow(el) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { UIOverlay } from './UIOverlay.js';
|
|
10
10
|
import { buildAssistantSnapshot, normalizeAssistant } from './AiAssistant.js';
|
|
11
11
|
|
|
12
|
-
export const BEE_LADYBUG_VERSION = '0.5.
|
|
12
|
+
export const BEE_LADYBUG_VERSION = '0.5.3';
|
|
13
13
|
|
|
14
14
|
export const CORE_DEFAULTS = Object.freeze({
|
|
15
15
|
toggleKey: 'F2',
|
|
@@ -364,7 +364,7 @@ export class BeeLadybugCore {
|
|
|
364
364
|
* @param {Array<{ text: string, level?: string, onSelect?: () => void }>} items
|
|
365
365
|
*/
|
|
366
366
|
publishIssues(items) {
|
|
367
|
-
const list = Array.isArray(items) ? items
|
|
367
|
+
const list = Array.isArray(items) ? items : [];
|
|
368
368
|
this.#issues = list;
|
|
369
369
|
this.ui?.setIssues(this.#issues);
|
|
370
370
|
return this;
|
package/src/core/UIOverlay.js
CHANGED
|
@@ -35,6 +35,9 @@ const STYLES = `
|
|
|
35
35
|
top: 12px;
|
|
36
36
|
left: 12px;
|
|
37
37
|
width: 360px;
|
|
38
|
+
max-height: 50vh;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
38
41
|
background: rgba(7, 8, 12, 0.94);
|
|
39
42
|
border: 1px solid rgba(255, 59, 78, 0.55);
|
|
40
43
|
box-sizing: border-box;
|
|
@@ -62,6 +65,7 @@ const STYLES = `
|
|
|
62
65
|
display: flex;
|
|
63
66
|
align-items: center;
|
|
64
67
|
gap: 8px;
|
|
68
|
+
flex: 0 0 auto;
|
|
65
69
|
padding: 10px 12px 8px;
|
|
66
70
|
border-bottom: 1px solid rgba(255, 59, 78, 0.28);
|
|
67
71
|
}
|
|
@@ -201,7 +205,7 @@ const STYLES = `
|
|
|
201
205
|
.bl-k { color: #8b91a3; }
|
|
202
206
|
.bl-v { color: #e9edf5; }
|
|
203
207
|
.bl-console {
|
|
204
|
-
height:
|
|
208
|
+
max-height: 140px;
|
|
205
209
|
overflow: auto;
|
|
206
210
|
padding: 6px 10px 8px;
|
|
207
211
|
background: #05060a;
|
|
@@ -212,13 +216,14 @@ const STYLES = `
|
|
|
212
216
|
.bl-line { white-space: normal; word-break: break-word; }
|
|
213
217
|
.bl-line .t { color: #8b91a3; margin-right: 8px; }
|
|
214
218
|
.bl-line .s { color: #6ee7ff; margin-right: 8px; }
|
|
215
|
-
.bl-line.info .m { color: #
|
|
219
|
+
.bl-line.info .m { color: #8b91a3; }
|
|
216
220
|
.bl-line.warn .m { color: #ffd36a; }
|
|
217
221
|
.bl-line.error .m { color: #ff6b7d; }
|
|
218
222
|
.bl-line.debug .m { color: #8b91a3; }
|
|
219
223
|
.bl-foot {
|
|
220
224
|
display: flex;
|
|
221
225
|
align-items: center;
|
|
226
|
+
flex: 0 0 auto;
|
|
222
227
|
flex-wrap: wrap;
|
|
223
228
|
gap: 6px;
|
|
224
229
|
padding: 8px 10px;
|
|
@@ -263,35 +268,72 @@ const STYLES = `
|
|
|
263
268
|
.bl-hotkeys:empty {
|
|
264
269
|
display: none;
|
|
265
270
|
}
|
|
271
|
+
.bl-scroll {
|
|
272
|
+
overflow: auto;
|
|
273
|
+
min-height: 0;
|
|
274
|
+
flex: 1 1 auto;
|
|
275
|
+
}
|
|
276
|
+
.bl-section[hidden] { display: none; }
|
|
277
|
+
.bl-section-toggle {
|
|
278
|
+
display: block;
|
|
279
|
+
width: 100%;
|
|
280
|
+
margin: 0;
|
|
281
|
+
padding: 5px 10px;
|
|
282
|
+
border: 0;
|
|
283
|
+
border-top: 1px solid rgba(255, 59, 78, 0.28);
|
|
284
|
+
background: #101218;
|
|
285
|
+
color: #ffd36a;
|
|
286
|
+
font: 700 9px ui-monospace, Consolas, monospace;
|
|
287
|
+
letter-spacing: 0.14em;
|
|
288
|
+
text-align: left;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
}
|
|
291
|
+
.bl-section-toggle::before { content: "▾ "; }
|
|
292
|
+
.bl-section[data-open="false"] > .bl-section-toggle::before { content: "▸ "; }
|
|
293
|
+
.bl-section[data-open="false"] > .bl-section-body { display: none; }
|
|
266
294
|
.bl-issues {
|
|
267
|
-
overflow: hidden;
|
|
268
295
|
background: #07080c;
|
|
269
|
-
border-top: 1px solid rgba(255, 59, 78, 0.55);
|
|
270
296
|
}
|
|
271
|
-
.bl-
|
|
272
|
-
display:
|
|
297
|
+
.bl-legend {
|
|
298
|
+
display: flex;
|
|
299
|
+
gap: 14px;
|
|
300
|
+
padding: 4px 10px 2px;
|
|
301
|
+
color: #8b91a3;
|
|
302
|
+
font-size: 10px;
|
|
273
303
|
}
|
|
274
|
-
.bl-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
304
|
+
.bl-dot {
|
|
305
|
+
display: inline-block;
|
|
306
|
+
width: 7px;
|
|
307
|
+
height: 7px;
|
|
308
|
+
margin-right: 5px;
|
|
309
|
+
border-radius: 50%;
|
|
310
|
+
vertical-align: baseline;
|
|
280
311
|
}
|
|
281
|
-
.bl-
|
|
312
|
+
.bl-dot.bug { background: #ff3b4e; }
|
|
313
|
+
.bl-dot.info { background: #8b91a3; }
|
|
314
|
+
.bl-cause > button,
|
|
315
|
+
.bl-cause-kids button,
|
|
316
|
+
.bl-note {
|
|
282
317
|
display: block;
|
|
283
318
|
width: 100%;
|
|
284
319
|
margin: 0;
|
|
285
320
|
padding: 3px 10px;
|
|
286
321
|
border: 0;
|
|
287
322
|
background: transparent;
|
|
288
|
-
color: #ff6b7d;
|
|
289
323
|
font: 11px ui-monospace, Consolas, monospace;
|
|
290
324
|
line-height: 1.4;
|
|
291
325
|
text-align: left;
|
|
326
|
+
}
|
|
327
|
+
.bl-cause > button,
|
|
328
|
+
.bl-cause-kids button {
|
|
329
|
+
color: #ff6b7d;
|
|
292
330
|
cursor: pointer;
|
|
293
331
|
}
|
|
294
|
-
.bl-
|
|
332
|
+
.bl-cause-kids[hidden] { display: none; }
|
|
333
|
+
.bl-cause-kids button { padding-left: 22px; }
|
|
334
|
+
.bl-cause > button:hover,
|
|
335
|
+
.bl-cause-kids button:hover { background: rgba(255, 59, 78, 0.08); }
|
|
336
|
+
.bl-note { color: #8b91a3; }
|
|
295
337
|
.bl-detail {
|
|
296
338
|
max-height: 132px;
|
|
297
339
|
overflow: auto;
|
|
@@ -396,6 +438,8 @@ export class UIOverlay {
|
|
|
396
438
|
this.#hotkeySig = '';
|
|
397
439
|
this.#detail = [];
|
|
398
440
|
this.#issues = [];
|
|
441
|
+
this.#issueNotes = [];
|
|
442
|
+
this.#openCauses = new Set();
|
|
399
443
|
this.#flashAt = new Map();
|
|
400
444
|
this.#flashTimers = new Map();
|
|
401
445
|
}
|
|
@@ -411,6 +455,8 @@ export class UIOverlay {
|
|
|
411
455
|
#hotkeySig;
|
|
412
456
|
#detail;
|
|
413
457
|
#issues;
|
|
458
|
+
#issueNotes;
|
|
459
|
+
#openCauses;
|
|
414
460
|
#flashAt;
|
|
415
461
|
#flashTimers;
|
|
416
462
|
|
|
@@ -447,6 +493,10 @@ export class UIOverlay {
|
|
|
447
493
|
</button>
|
|
448
494
|
</span>
|
|
449
495
|
</div>
|
|
496
|
+
<div class="bl-scroll">
|
|
497
|
+
<section class="bl-section" data-section="status" data-open="true">
|
|
498
|
+
<button class="bl-section-toggle" type="button" data-section-toggle aria-expanded="true">STATUS</button>
|
|
499
|
+
<div class="bl-section-body">
|
|
450
500
|
<div class="bl-stats">
|
|
451
501
|
<div>HOST <b data-el="hostFps">—</b></div>
|
|
452
502
|
<div>CORE <b data-el="coreFps">—</b></div>
|
|
@@ -461,12 +511,32 @@ export class UIOverlay {
|
|
|
461
511
|
</div>
|
|
462
512
|
</div>
|
|
463
513
|
<div class="bl-hud" data-el="hud"></div>
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
514
|
+
</div>
|
|
515
|
+
</section>
|
|
516
|
+
<section class="bl-section" data-el="issues" data-section="issues" data-open="true" hidden>
|
|
517
|
+
<button class="bl-section-toggle" type="button" data-section-toggle aria-expanded="true">ISSUES</button>
|
|
518
|
+
<div class="bl-section-body">
|
|
519
|
+
<div class="bl-legend">
|
|
520
|
+
<span><i class="bl-dot bug"></i>bug</span>
|
|
521
|
+
<span><i class="bl-dot info"></i>info</span>
|
|
522
|
+
</div>
|
|
523
|
+
<div data-el="issues-list"></div>
|
|
524
|
+
<div data-el="notes"></div>
|
|
525
|
+
</div>
|
|
526
|
+
</section>
|
|
527
|
+
<section class="bl-section" data-el="inspect" data-section="inspect" data-open="true" hidden>
|
|
528
|
+
<button class="bl-section-toggle" type="button" data-section-toggle aria-expanded="true">INSPECT</button>
|
|
529
|
+
<div class="bl-section-body">
|
|
530
|
+
<div class="bl-detail" data-el="detail" hidden></div>
|
|
531
|
+
</div>
|
|
532
|
+
</section>
|
|
533
|
+
<section class="bl-section" data-section="events" data-open="true">
|
|
534
|
+
<button class="bl-section-toggle" type="button" data-section-toggle aria-expanded="true">EVENTS</button>
|
|
535
|
+
<div class="bl-section-body">
|
|
536
|
+
<div class="bl-console" data-el="console" role="log" aria-live="polite"></div>
|
|
537
|
+
</div>
|
|
538
|
+
</section>
|
|
467
539
|
</div>
|
|
468
|
-
<div class="bl-detail" data-el="detail" hidden></div>
|
|
469
|
-
<div class="bl-console" data-el="console" role="log" aria-live="polite"></div>
|
|
470
540
|
<div class="bl-foot">
|
|
471
541
|
<button class="bl-btn" data-act="slow" type="button">F3 SLOW</button>
|
|
472
542
|
<button class="bl-btn" data-act="freeze" type="button">F4 STOP</button>
|
|
@@ -494,8 +564,10 @@ export class UIOverlay {
|
|
|
494
564
|
hud: this.#root.querySelector('[data-el="hud"]'),
|
|
495
565
|
console: this.#root.querySelector('[data-el="console"]'),
|
|
496
566
|
detail: this.#root.querySelector('[data-el="detail"]'),
|
|
567
|
+
inspect: this.#root.querySelector('[data-el="inspect"]'),
|
|
497
568
|
issues: this.#root.querySelector('[data-el="issues"]'),
|
|
498
569
|
issuesList: this.#root.querySelector('[data-el="issues-list"]'),
|
|
570
|
+
notes: this.#root.querySelector('[data-el="notes"]'),
|
|
499
571
|
providers: this.#root.querySelector('[data-el="providers"]'),
|
|
500
572
|
adapters: this.#root.querySelector('[data-el="adapters"]'),
|
|
501
573
|
hotkeys: this.#root.querySelector('[data-el="hotkeys"]')
|
|
@@ -513,11 +585,40 @@ export class UIOverlay {
|
|
|
513
585
|
else if (act === 'adapter') this.core.toggleAdapter(btn.getAttribute('data-name'));
|
|
514
586
|
});
|
|
515
587
|
|
|
516
|
-
this.#root.querySelector('.bl-
|
|
588
|
+
this.#root.querySelector('.bl-scroll')?.addEventListener('click', (event) => {
|
|
589
|
+
const toggle = event.target.closest('[data-section-toggle]');
|
|
590
|
+
if (!toggle) return;
|
|
591
|
+
const section = toggle.closest('.bl-section');
|
|
592
|
+
if (!section) return;
|
|
593
|
+
const open = section.dataset.open === 'false';
|
|
594
|
+
section.dataset.open = open ? 'true' : 'false';
|
|
595
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
this.#els.issues?.addEventListener('click', (event) => {
|
|
599
|
+
if (event.target.closest('[data-section-toggle]')) return;
|
|
600
|
+
const childBtn = event.target.closest('[data-target]');
|
|
601
|
+
if (childBtn) {
|
|
602
|
+
const item = this.#issues[Number(childBtn.getAttribute('data-issue'))];
|
|
603
|
+
const child = item?.targets?.[Number(childBtn.getAttribute('data-target'))];
|
|
604
|
+
if (child && typeof child.onSelect === 'function') child.onSelect();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
517
607
|
const row = event.target.closest('[data-issue]');
|
|
518
608
|
if (!row) return;
|
|
519
609
|
const item = this.#issues[Number(row.getAttribute('data-issue'))];
|
|
520
|
-
if (item
|
|
610
|
+
if (!item) return;
|
|
611
|
+
const many = Array.isArray(item.targets) && item.targets.length > 1;
|
|
612
|
+
if (many) {
|
|
613
|
+
const key = item.key || item.text;
|
|
614
|
+
if (this.#openCauses.has(key)) this.#openCauses.delete(key);
|
|
615
|
+
else this.#openCauses.add(key);
|
|
616
|
+
this.setIssues(this.#issues.concat(this.#issueNotes));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const only = item.targets?.[0];
|
|
620
|
+
if (only && typeof only.onSelect === 'function') only.onSelect();
|
|
621
|
+
else if (typeof item.onSelect === 'function') item.onSelect();
|
|
521
622
|
});
|
|
522
623
|
|
|
523
624
|
this.#root.querySelector('.bl-detail')?.addEventListener('click', (event) => {
|
|
@@ -551,6 +652,8 @@ export class UIOverlay {
|
|
|
551
652
|
this.#hotkeySig = '';
|
|
552
653
|
this.#detail = [];
|
|
553
654
|
this.#issues = [];
|
|
655
|
+
this.#issueNotes = [];
|
|
656
|
+
this.#openCauses.clear();
|
|
554
657
|
this.#clearFlashTimers();
|
|
555
658
|
this.#flashAt.clear();
|
|
556
659
|
return this;
|
|
@@ -576,9 +679,11 @@ export class UIOverlay {
|
|
|
576
679
|
const list = Array.isArray(items) ? items : [];
|
|
577
680
|
this.#detail = list;
|
|
578
681
|
const box = this.#els.detail;
|
|
682
|
+
const section = this.#els.inspect;
|
|
579
683
|
if (!list.length) {
|
|
580
684
|
box.innerHTML = '';
|
|
581
685
|
box.hidden = true;
|
|
686
|
+
if (section) section.hidden = true;
|
|
582
687
|
return this;
|
|
583
688
|
}
|
|
584
689
|
let html = '';
|
|
@@ -587,30 +692,58 @@ export class UIOverlay {
|
|
|
587
692
|
html += `<button type="button" data-detail="${i}" data-level="${level}">${escapeHtml(list[i].text)}</button>`;
|
|
588
693
|
}
|
|
589
694
|
box.hidden = false;
|
|
695
|
+
if (section) section.hidden = false;
|
|
590
696
|
box.innerHTML = html;
|
|
591
697
|
return this;
|
|
592
698
|
}
|
|
593
699
|
|
|
594
700
|
/**
|
|
595
|
-
*
|
|
596
|
-
* @param {Array<{ text: string, level?: string, onSelect?: () => void }>} items
|
|
701
|
+
* Cause rows plus grey info notes. Bugs are red and expand; info stays grey.
|
|
702
|
+
* @param {Array<{ text: string, level?: string, key?: string, onSelect?: () => void, targets?: Array<{ text: string, onSelect?: () => void }> }>} items
|
|
597
703
|
*/
|
|
598
704
|
setIssues(items) {
|
|
599
|
-
if (!this.#els?.issues || !this.#els.issuesList) return this;
|
|
600
|
-
const list = Array.isArray(items) ? items
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
705
|
+
if (!this.#els?.issues || !this.#els.issuesList || !this.#els.notes) return this;
|
|
706
|
+
const list = Array.isArray(items) ? items : [];
|
|
707
|
+
const bugs = [];
|
|
708
|
+
const notes = [];
|
|
709
|
+
for (let i = 0; i < list.length; i++) {
|
|
710
|
+
if (list[i].level === 'info') notes.push(list[i]);
|
|
711
|
+
else bugs.push(list[i]);
|
|
712
|
+
}
|
|
713
|
+
const capped = bugs.slice(0, 10);
|
|
714
|
+
this.#issues = capped;
|
|
715
|
+
this.#issueNotes = notes;
|
|
716
|
+
const section = this.#els.issues;
|
|
717
|
+
if (!capped.length && !notes.length) {
|
|
604
718
|
this.#els.issuesList.innerHTML = '';
|
|
605
|
-
|
|
719
|
+
this.#els.notes.innerHTML = '';
|
|
720
|
+
section.hidden = true;
|
|
606
721
|
return this;
|
|
607
722
|
}
|
|
608
723
|
let html = '';
|
|
609
|
-
for (let i = 0; i <
|
|
610
|
-
|
|
724
|
+
for (let i = 0; i < capped.length; i++) {
|
|
725
|
+
const item = capped[i];
|
|
726
|
+
const targets = Array.isArray(item.targets) ? item.targets : [];
|
|
727
|
+
const many = targets.length > 1;
|
|
728
|
+
const open = many && this.#openCauses.has(item.key || item.text);
|
|
729
|
+
html += `<div class="bl-cause" data-open="${open ? 'true' : 'false'}">`;
|
|
730
|
+
html += `<button type="button" data-issue="${i}">${many ? (open ? '▾ ' : '▸ ') : ''}${escapeHtml(item.text)}</button>`;
|
|
731
|
+
if (many) {
|
|
732
|
+
html += `<div class="bl-cause-kids"${open ? '' : ' hidden'}>`;
|
|
733
|
+
for (let j = 0; j < targets.length; j++) {
|
|
734
|
+
html += `<button type="button" data-issue="${i}" data-target="${j}">${escapeHtml(targets[j].text)}</button>`;
|
|
735
|
+
}
|
|
736
|
+
html += '</div>';
|
|
737
|
+
}
|
|
738
|
+
html += '</div>';
|
|
611
739
|
}
|
|
612
|
-
box.hidden = false;
|
|
613
740
|
this.#els.issuesList.innerHTML = html;
|
|
741
|
+
let noteHtml = '';
|
|
742
|
+
for (let i = 0; i < notes.length; i++) {
|
|
743
|
+
noteHtml += `<div class="bl-note">${escapeHtml(notes[i].text)}</div>`;
|
|
744
|
+
}
|
|
745
|
+
this.#els.notes.innerHTML = noteHtml;
|
|
746
|
+
section.hidden = false;
|
|
614
747
|
return this;
|
|
615
748
|
}
|
|
616
749
|
|
package/src/index.d.ts
CHANGED