bee-ladybug 0.6.0 → 0.6.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.
package/package.json
CHANGED
|
@@ -219,9 +219,9 @@ export class CSSAdapter {
|
|
|
219
219
|
const findings = [];
|
|
220
220
|
pushNegativeMargins(el, findings);
|
|
221
221
|
pushInvisible(el, findings);
|
|
222
|
-
pushBlockers(el, findings);
|
|
222
|
+
pushBlockers(el, findings, true);
|
|
223
223
|
pushImage(el, findings);
|
|
224
|
-
pushStacking(el, findings);
|
|
224
|
+
pushStacking(el, findings, true);
|
|
225
225
|
pushImportant(el, built.rules, findings);
|
|
226
226
|
for (let j = 0; j < findings.length; j++) {
|
|
227
227
|
rows.push({ el, selector: cssPath(el), ...findings[j] });
|
|
@@ -755,7 +755,7 @@ function pushInvisible(el, findings) {
|
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
-
function pushBlockers(el, findings) {
|
|
758
|
+
function pushBlockers(el, findings, skipNoise = false) {
|
|
759
759
|
if (!el.getBoundingClientRect || typeof document.elementFromPoint !== 'function') return;
|
|
760
760
|
const rect = el.getBoundingClientRect();
|
|
761
761
|
const x = rect.left + rect.width / 2;
|
|
@@ -768,6 +768,7 @@ function pushBlockers(el, findings) {
|
|
|
768
768
|
const hit = document.elementFromPoint(x, y);
|
|
769
769
|
if (!hit || isBeeChrome(hit) || hit === el || el.contains(hit)) return;
|
|
770
770
|
if (pseudo && (hit === document.documentElement || hit === document.body)) return;
|
|
771
|
+
if (skipNoise && isReportNoise(hit)) return;
|
|
771
772
|
findings.push(issue(`click blocked by ${cssPath(hit)}`, ISSUE_RANK.blocker, 40));
|
|
772
773
|
}
|
|
773
774
|
|
|
@@ -950,7 +951,7 @@ function willChangeHint(value) {
|
|
|
950
951
|
return hit.join(', ');
|
|
951
952
|
}
|
|
952
953
|
|
|
953
|
-
function pushStacking(el, findings) {
|
|
954
|
+
function pushStacking(el, findings, skipNoise = false) {
|
|
954
955
|
if (!el || typeof getComputedStyle !== 'function' || !el.parentElement) return;
|
|
955
956
|
const own = getComputedStyle(el);
|
|
956
957
|
const ownZ = own.zIndex || own.getPropertyValue?.('z-index') || 'auto';
|
|
@@ -958,6 +959,7 @@ function pushStacking(el, findings) {
|
|
|
958
959
|
let node = el.parentElement;
|
|
959
960
|
while (node && node.nodeType === 1) {
|
|
960
961
|
if (isBeeChrome(node)) return;
|
|
962
|
+
if (skipNoise && isReportNoise(node)) return;
|
|
961
963
|
const found = stackingReasons(getComputedStyle(node));
|
|
962
964
|
const ancestor = cssPath(node);
|
|
963
965
|
if (found.bugs.length) {
|
|
@@ -70,6 +70,8 @@ export class CanvasAdapter {
|
|
|
70
70
|
this.#fps = 0;
|
|
71
71
|
this.#fpsSamples = [];
|
|
72
72
|
this.#wasColliding = new Set();
|
|
73
|
+
this.#names = new Map();
|
|
74
|
+
this.#overlayStyle = null;
|
|
73
75
|
this.#onResize = () => this.#syncOverlay();
|
|
74
76
|
this.#onTick = () => {
|
|
75
77
|
this.#rafId = requestAnimationFrame(this.#onTick);
|
|
@@ -93,6 +95,10 @@ export class CanvasAdapter {
|
|
|
93
95
|
#fps;
|
|
94
96
|
#fpsSamples;
|
|
95
97
|
#wasColliding;
|
|
98
|
+
/** id -> display name, refreshed on every pump. */
|
|
99
|
+
#names;
|
|
100
|
+
/** Last style values written to the overlay. */
|
|
101
|
+
#overlayStyle;
|
|
96
102
|
#onResize;
|
|
97
103
|
#onTick;
|
|
98
104
|
|
|
@@ -126,6 +132,12 @@ export class CanvasAdapter {
|
|
|
126
132
|
source: this.source,
|
|
127
133
|
message: `CanvasAdapter attached (${this.entities.length} entities).`
|
|
128
134
|
});
|
|
135
|
+
if (this.overlayEnabled && !this.canvas) {
|
|
136
|
+
this.core?.sendData('warn', {
|
|
137
|
+
source: this.source,
|
|
138
|
+
message: 'no canvas: hitboxes are not drawn. Pass { canvas } or call setCanvas().'
|
|
139
|
+
});
|
|
140
|
+
}
|
|
129
141
|
this.core?.sendData('state', {
|
|
130
142
|
source: this.source,
|
|
131
143
|
key: 'adapter',
|
|
@@ -146,6 +158,11 @@ export class CanvasAdapter {
|
|
|
146
158
|
window.removeEventListener('resize', this.#onResize);
|
|
147
159
|
window.removeEventListener('scroll', this.#onResize, true);
|
|
148
160
|
}
|
|
161
|
+
this.#wasColliding = new Set();
|
|
162
|
+
this.#names = new Map();
|
|
163
|
+
this.#lastPump = 0;
|
|
164
|
+
this.#fps = 0;
|
|
165
|
+
this.#fpsSamples = [];
|
|
149
166
|
return this;
|
|
150
167
|
}
|
|
151
168
|
|
|
@@ -163,10 +180,26 @@ export class CanvasAdapter {
|
|
|
163
180
|
return this;
|
|
164
181
|
}
|
|
165
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Stop the internal loop and make a manual `pump()` valid again.
|
|
185
|
+
* Mirror of `startAutoPump()`.
|
|
186
|
+
*/
|
|
187
|
+
stopAutoPump() {
|
|
188
|
+
this.autoPump = false;
|
|
189
|
+
this.#stopAutoPumpLoop();
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
166
193
|
/** @param {HTMLCanvasElement | null} canvas */
|
|
167
194
|
setCanvas(canvas) {
|
|
168
195
|
this.canvas = canvas;
|
|
169
|
-
this.#
|
|
196
|
+
if (!this.#attached) return this;
|
|
197
|
+
if (!canvas) {
|
|
198
|
+
this.#unmountOverlay();
|
|
199
|
+
} else if (this.overlayEnabled) {
|
|
200
|
+
if (this.#overlay) this.#syncOverlay();
|
|
201
|
+
else this.#mountOverlay();
|
|
202
|
+
}
|
|
170
203
|
return this;
|
|
171
204
|
}
|
|
172
205
|
|
|
@@ -190,7 +223,7 @@ export class CanvasAdapter {
|
|
|
190
223
|
severity: box.hit ? 'warn' : 'ok',
|
|
191
224
|
selector: box.name,
|
|
192
225
|
rule: 'hitbox',
|
|
193
|
-
value: `${box.x},${box.y} ${box.w}x${box.h}${box.hit ? ' colliding' : ''}`,
|
|
226
|
+
value: `${round1(box.x)},${round1(box.y)} ${round1(box.w)}x${round1(box.h)}${box.hit ? ' colliding' : ''}`,
|
|
194
227
|
source: this.source
|
|
195
228
|
}));
|
|
196
229
|
const fps = this.#fpsItem();
|
|
@@ -327,7 +360,9 @@ export class CanvasAdapter {
|
|
|
327
360
|
|
|
328
361
|
#emitCollisionEdges(boxes) {
|
|
329
362
|
const now = new Set();
|
|
363
|
+
const names = new Map();
|
|
330
364
|
for (let i = 0; i < boxes.length; i++) {
|
|
365
|
+
names.set(boxes[i].id, boxes[i].name);
|
|
331
366
|
if (!boxes[i].hit) continue;
|
|
332
367
|
now.add(boxes[i].id);
|
|
333
368
|
if (!this.#wasColliding.has(boxes[i].id)) {
|
|
@@ -339,13 +374,14 @@ export class CanvasAdapter {
|
|
|
339
374
|
}
|
|
340
375
|
for (const id of this.#wasColliding) {
|
|
341
376
|
if (now.has(id)) continue;
|
|
342
|
-
const name =
|
|
377
|
+
const name = names.get(id) ?? this.#names.get(id) ?? id;
|
|
343
378
|
this.core.sendData('log', {
|
|
344
379
|
source: this.source,
|
|
345
380
|
message: `${name} left collision`
|
|
346
381
|
});
|
|
347
382
|
}
|
|
348
383
|
this.#wasColliding = now;
|
|
384
|
+
this.#names = names;
|
|
349
385
|
}
|
|
350
386
|
|
|
351
387
|
#drawHitboxes(boxes, hostCtx) {
|
|
@@ -386,7 +422,7 @@ export class CanvasAdapter {
|
|
|
386
422
|
}
|
|
387
423
|
|
|
388
424
|
#mountOverlay() {
|
|
389
|
-
if (typeof document === 'undefined' || this.#overlay) return;
|
|
425
|
+
if (typeof document === 'undefined' || this.#overlay || !this.canvas) return;
|
|
390
426
|
const canvas = document.createElement('canvas');
|
|
391
427
|
canvas.setAttribute('data-bee-ladybug-canvas', '');
|
|
392
428
|
canvas.style.pointerEvents = 'none';
|
|
@@ -395,12 +431,14 @@ export class CanvasAdapter {
|
|
|
395
431
|
canvas.style.margin = '0';
|
|
396
432
|
document.body.appendChild(canvas);
|
|
397
433
|
this.#overlay = canvas;
|
|
434
|
+
this.#overlayStyle = null;
|
|
398
435
|
this.#syncOverlay();
|
|
399
436
|
}
|
|
400
437
|
|
|
401
438
|
#unmountOverlay() {
|
|
402
439
|
if (this.#overlay?.parentNode) this.#overlay.parentNode.removeChild(this.#overlay);
|
|
403
440
|
this.#overlay = null;
|
|
441
|
+
this.#overlayStyle = null;
|
|
404
442
|
}
|
|
405
443
|
|
|
406
444
|
#syncOverlay() {
|
|
@@ -408,14 +446,22 @@ export class CanvasAdapter {
|
|
|
408
446
|
const overlay = this.#overlay;
|
|
409
447
|
if (!host || !overlay) return;
|
|
410
448
|
const rect = host.getBoundingClientRect();
|
|
411
|
-
|
|
412
|
-
overlay.
|
|
413
|
-
overlay.
|
|
414
|
-
overlay.style.top = `${rect.top}px`;
|
|
415
|
-
overlay.style.width = `${rect.width}px`;
|
|
416
|
-
overlay.style.height = `${rect.height}px`;
|
|
449
|
+
// Assigning width/height clears and reallocates the bitmap: only when they change.
|
|
450
|
+
if (overlay.width !== host.width) overlay.width = host.width;
|
|
451
|
+
if (overlay.height !== host.height) overlay.height = host.height;
|
|
417
452
|
const hidden = !this.#reporting || (this.core && this.core.visible === false);
|
|
418
|
-
|
|
453
|
+
const next = {
|
|
454
|
+
left: `${rect.left}px`,
|
|
455
|
+
top: `${rect.top}px`,
|
|
456
|
+
width: `${rect.width}px`,
|
|
457
|
+
height: `${rect.height}px`,
|
|
458
|
+
display: hidden ? 'none' : 'block'
|
|
459
|
+
};
|
|
460
|
+
const prev = this.#overlayStyle;
|
|
461
|
+
for (const key of Object.keys(next)) {
|
|
462
|
+
if (!prev || prev[key] !== next[key]) overlay.style[key] = next[key];
|
|
463
|
+
}
|
|
464
|
+
this.#overlayStyle = next;
|
|
419
465
|
}
|
|
420
466
|
|
|
421
467
|
#enable() {
|
|
@@ -487,11 +533,6 @@ function isPrimary(entity) {
|
|
|
487
533
|
return key === 'player' || key === 'hero' || entity.primary === true;
|
|
488
534
|
}
|
|
489
535
|
|
|
490
|
-
function idFromKey(id) {
|
|
491
|
-
const parts = String(id).split(':');
|
|
492
|
-
return parts[parts.length - 1] || id;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
536
|
function round1(value) {
|
|
496
537
|
return Math.round(Number(value) * 10) / 10;
|
|
497
538
|
}
|
|
@@ -10,7 +10,7 @@ import { UIOverlay } from './UIOverlay.js';
|
|
|
10
10
|
import { buildAssistantSnapshot, normalizeAssistant } from './AiAssistant.js';
|
|
11
11
|
import { exportReport as writeReport } from './ReportExporter.js';
|
|
12
12
|
|
|
13
|
-
export const BEE_LADYBUG_VERSION = '0.6.
|
|
13
|
+
export const BEE_LADYBUG_VERSION = '0.6.2';
|
|
14
14
|
|
|
15
15
|
export const CORE_DEFAULTS = Object.freeze({
|
|
16
16
|
toggleKey: 'F2',
|
|
@@ -74,11 +74,19 @@ export function isReportNoise(node) {
|
|
|
74
74
|
while (current && typeof current === 'object') {
|
|
75
75
|
if (seen.has(current)) break;
|
|
76
76
|
seen.add(current);
|
|
77
|
-
if (current.id === 'wpadminbar') return true;
|
|
78
|
-
if (hasAbClass(current
|
|
77
|
+
if (current.id === 'wpadminbar' || elementId(current) === 'wpadminbar') return true;
|
|
78
|
+
if (hasAbClass(classText(current))) return true;
|
|
79
79
|
if (current.tagName === 'BEE-LADYBUG-OVERLAY') return true;
|
|
80
80
|
if (hasBeeAttribute(current)) return true;
|
|
81
|
-
|
|
81
|
+
if (typeof current.closest === 'function') {
|
|
82
|
+
try {
|
|
83
|
+
const bar = current.closest('#wpadminbar');
|
|
84
|
+
if (bar) return true;
|
|
85
|
+
} catch {
|
|
86
|
+
/* closest is optional */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
current = current.parentElement || (current.parentNode && current.parentNode.nodeType === 1 ? current.parentNode : null);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
const style = readStyle(el);
|
|
@@ -103,7 +111,7 @@ function buildReport(core) {
|
|
|
103
111
|
const series = readSeries(core);
|
|
104
112
|
const latest = readLatest(core);
|
|
105
113
|
const adapters = readAdapters(core);
|
|
106
|
-
const summary = countFindings(adapters);
|
|
114
|
+
const summary = countFindings(adapters, logs);
|
|
107
115
|
|
|
108
116
|
return {
|
|
109
117
|
version: state.version,
|
|
@@ -219,7 +227,7 @@ function readAdapters(core) {
|
|
|
219
227
|
const list = result && Array.isArray(result.items) ? result.items : [];
|
|
220
228
|
sections.push({
|
|
221
229
|
name,
|
|
222
|
-
items: list.filter((row) => row && typeof row === 'object').map((row) => makeItem(row, name))
|
|
230
|
+
items: groupIdentical(list.filter((row) => row && typeof row === 'object').map((row) => makeItem(row, name)))
|
|
223
231
|
});
|
|
224
232
|
}
|
|
225
233
|
return sections;
|
|
@@ -240,14 +248,45 @@ function makeItem(row, fallbackSource = '') {
|
|
|
240
248
|
};
|
|
241
249
|
}
|
|
242
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Identical selector + rule + value collapse to one row. The count is part of the value.
|
|
253
|
+
* @param {ReportItem[]} items
|
|
254
|
+
* @returns {ReportItem[]}
|
|
255
|
+
*/
|
|
256
|
+
function groupIdentical(items) {
|
|
257
|
+
const order = [];
|
|
258
|
+
const map = new Map();
|
|
259
|
+
for (let i = 0; i < items.length; i++) {
|
|
260
|
+
const item = items[i];
|
|
261
|
+
const key = `${item.selector}\0${item.rule}\0${item.value}`;
|
|
262
|
+
const prev = map.get(key);
|
|
263
|
+
if (!prev) {
|
|
264
|
+
const row = { ...item, count: 1 };
|
|
265
|
+
map.set(key, row);
|
|
266
|
+
order.push(row);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
prev.count += 1;
|
|
270
|
+
}
|
|
271
|
+
return order.map((row) => ({
|
|
272
|
+
severity: row.severity,
|
|
273
|
+
selector: row.selector,
|
|
274
|
+
rule: row.rule,
|
|
275
|
+
value: row.count > 1 ? `${row.value} ×${row.count}` : row.value,
|
|
276
|
+
source: row.source
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
|
|
243
280
|
/**
|
|
244
281
|
* @param {Array<{ items: ReportItem[] }>} adapters
|
|
282
|
+
* @param {Array<{ type: string, level: string | null }>} logs
|
|
245
283
|
*/
|
|
246
|
-
function countFindings(adapters) {
|
|
284
|
+
function countFindings(adapters, logs) {
|
|
247
285
|
const summary = { error: 0, warn: 0, ok: 0 };
|
|
248
286
|
for (const section of adapters) {
|
|
249
287
|
for (const item of section.items) summary[item.severity] += 1;
|
|
250
288
|
}
|
|
289
|
+
for (let i = 0; i < logs.length; i++) summary[logSeverity(logs[i])] += 1;
|
|
251
290
|
return summary;
|
|
252
291
|
}
|
|
253
292
|
|
|
@@ -267,7 +306,7 @@ function normalizeFormat(format) {
|
|
|
267
306
|
function normalizeSeverity(value) {
|
|
268
307
|
const text = String(value || '').toLowerCase();
|
|
269
308
|
if (text === 'error') return 'error';
|
|
270
|
-
if (text === 'warn' || text === 'warning') return 'warn';
|
|
309
|
+
if (text === 'warn' || text === 'warning' || text === 'warm') return 'warn';
|
|
271
310
|
return 'ok';
|
|
272
311
|
}
|
|
273
312
|
|
|
@@ -332,8 +371,10 @@ function csvCell(value) {
|
|
|
332
371
|
* @returns {ReportSeverity}
|
|
333
372
|
*/
|
|
334
373
|
function logSeverity(log) {
|
|
335
|
-
|
|
336
|
-
if (
|
|
374
|
+
const level = String(log.level || '').toLowerCase();
|
|
375
|
+
if (level === 'warn' || level === 'warning' || level === 'warm') return 'warn';
|
|
376
|
+
if (level === 'error' || log.type === 'error') return 'error';
|
|
377
|
+
if (log.type === 'warn') return 'warn';
|
|
337
378
|
return 'ok';
|
|
338
379
|
}
|
|
339
380
|
|
|
@@ -490,11 +531,22 @@ function saveIfMounted(core, report, ext, mime, text) {
|
|
|
490
531
|
if (typeof document === 'undefined' || !document.body) return;
|
|
491
532
|
if (typeof URL === 'undefined' || typeof URL.createObjectURL !== 'function' || typeof Blob === 'undefined') return;
|
|
492
533
|
|
|
493
|
-
const
|
|
534
|
+
const filename = `report-${report.fileStamp}.${ext}`;
|
|
535
|
+
const type = `${mime};charset=utf-8`;
|
|
536
|
+
let blob;
|
|
537
|
+
try {
|
|
538
|
+
blob = typeof File === 'function'
|
|
539
|
+
? new File([text], filename, { type })
|
|
540
|
+
: new Blob([text], { type });
|
|
541
|
+
} catch {
|
|
542
|
+
if (typeof Blob !== 'function') return;
|
|
543
|
+
blob = new Blob([text], { type });
|
|
544
|
+
}
|
|
494
545
|
const url = URL.createObjectURL(blob);
|
|
495
546
|
const link = document.createElement('a');
|
|
496
547
|
link.href = url;
|
|
497
|
-
link.download =
|
|
548
|
+
link.download = filename;
|
|
549
|
+
link.setAttribute('download', filename);
|
|
498
550
|
link.rel = 'noopener';
|
|
499
551
|
document.body.appendChild(link);
|
|
500
552
|
link.click();
|
|
@@ -580,6 +632,34 @@ function escapeHtml(value) {
|
|
|
580
632
|
.replace(/"/g, '"');
|
|
581
633
|
}
|
|
582
634
|
|
|
635
|
+
function classText(node) {
|
|
636
|
+
if (!node || typeof node !== 'object') return '';
|
|
637
|
+
const parts = [];
|
|
638
|
+
if (typeof node.className === 'string' && node.className) parts.push(node.className);
|
|
639
|
+
else if (node.className && typeof node.className === 'object' && 'baseVal' in node.className && node.className.baseVal) {
|
|
640
|
+
parts.push(String(node.className.baseVal));
|
|
641
|
+
}
|
|
642
|
+
if (node.classList && typeof node.classList.length === 'number') {
|
|
643
|
+
try {
|
|
644
|
+
parts.push([...node.classList].join(' '));
|
|
645
|
+
} catch {
|
|
646
|
+
/* classList may not be iterable */
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (typeof node.getAttribute === 'function') {
|
|
650
|
+
const attr = node.getAttribute('class');
|
|
651
|
+
if (attr) parts.push(attr);
|
|
652
|
+
}
|
|
653
|
+
return parts.join(' ');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function elementId(node) {
|
|
657
|
+
if (!node || typeof node !== 'object') return '';
|
|
658
|
+
if (typeof node.id === 'string' && node.id) return node.id;
|
|
659
|
+
if (typeof node.getAttribute === 'function') return node.getAttribute('id') || '';
|
|
660
|
+
return '';
|
|
661
|
+
}
|
|
662
|
+
|
|
583
663
|
/**
|
|
584
664
|
* @param {unknown} className
|
|
585
665
|
*/
|
package/src/index.d.ts
CHANGED
|
@@ -334,6 +334,7 @@ export class CanvasAdapter {
|
|
|
334
334
|
attach(): this;
|
|
335
335
|
detach(): this;
|
|
336
336
|
startAutoPump(): this;
|
|
337
|
+
stopAutoPump(): this;
|
|
337
338
|
setCanvas(canvas: HTMLCanvasElement | null): this;
|
|
338
339
|
setEntities(entities: CanvasEntity[]): this;
|
|
339
340
|
pump(ctx?: CanvasRenderingContext2D): this;
|