bee-ladybug 0.6.3 → 0.6.4
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
|
@@ -189,6 +189,7 @@ function isBeeChrome(node) {
|
|
|
189
189
|
if (tag === 'BEE-LADYBUG-OVERLAY') return true;
|
|
190
190
|
if (el.hasAttribute?.('data-bee-ladybug-dom')) return true;
|
|
191
191
|
if (el.hasAttribute?.('data-bee-ladybug-canvas')) return true;
|
|
192
|
+
if (el.hasAttribute?.('data-bee-ladybug-css')) return true;
|
|
192
193
|
el = el.parentElement;
|
|
193
194
|
}
|
|
194
195
|
return false;
|
|
@@ -61,9 +61,13 @@ export class CSSAdapter {
|
|
|
61
61
|
this.#hoverBox = null;
|
|
62
62
|
this.#pinBox = null;
|
|
63
63
|
this.#observer = null;
|
|
64
|
+
this.#unsubscribe = null;
|
|
65
|
+
this.#hasPointer = false;
|
|
64
66
|
this.#onMove = (event) => {
|
|
67
|
+
if (this.core && this.core.visible === false) return;
|
|
65
68
|
this.#x = event.clientX;
|
|
66
69
|
this.#y = event.clientY;
|
|
70
|
+
this.#hasPointer = true;
|
|
67
71
|
this.#schedule();
|
|
68
72
|
};
|
|
69
73
|
this.#onKey = (event) => this.#handleKey(event);
|
|
@@ -71,7 +75,7 @@ export class CSSAdapter {
|
|
|
71
75
|
this.#onMutate = (records) => {
|
|
72
76
|
if (!records.some(touchesSheet)) return;
|
|
73
77
|
this.#sheetsDirty = true;
|
|
74
|
-
this.#schedule();
|
|
78
|
+
if (this.#hasPointer) this.#schedule();
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
81
|
|
|
@@ -92,6 +96,9 @@ export class CSSAdapter {
|
|
|
92
96
|
#hoverBox;
|
|
93
97
|
#pinBox;
|
|
94
98
|
#observer;
|
|
99
|
+
#unsubscribe;
|
|
100
|
+
/** True after the first mousemove: before that (x, y) is not a real pointer. */
|
|
101
|
+
#hasPointer;
|
|
95
102
|
#onMove;
|
|
96
103
|
#onKey;
|
|
97
104
|
#onScroll;
|
|
@@ -104,6 +111,9 @@ export class CSSAdapter {
|
|
|
104
111
|
attach() {
|
|
105
112
|
if (this.#attached) return this;
|
|
106
113
|
this.#attached = true;
|
|
114
|
+
if (this.core && typeof this.core.subscribe === 'function') {
|
|
115
|
+
this.#unsubscribe = this.core.subscribe((packet) => this.#onCore(packet));
|
|
116
|
+
}
|
|
107
117
|
this.core?.registerAdapter('css', {
|
|
108
118
|
label: this.label,
|
|
109
119
|
hotkey: this.hotkey,
|
|
@@ -128,10 +138,21 @@ export class CSSAdapter {
|
|
|
128
138
|
if (!this.#attached) return this;
|
|
129
139
|
this.#disable();
|
|
130
140
|
this.#attached = false;
|
|
141
|
+
if (this.#unsubscribe) this.#unsubscribe();
|
|
142
|
+
this.#unsubscribe = null;
|
|
131
143
|
this.core?.unregisterAdapter('css');
|
|
132
144
|
return this;
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
#onCore(packet) {
|
|
148
|
+
if (packet.type !== 'sys' || packet.payload?.op !== 'visibility') return;
|
|
149
|
+
this.#placeBoxes();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#boxesVisible() {
|
|
153
|
+
return !(this.core && this.core.visible === false);
|
|
154
|
+
}
|
|
155
|
+
|
|
135
156
|
/** Page scan. Click a row to outline one element of that group. */
|
|
136
157
|
scan() {
|
|
137
158
|
if (!this.#active || typeof document === 'undefined') return this;
|
|
@@ -141,15 +162,15 @@ export class CSSAdapter {
|
|
|
141
162
|
const rows = [];
|
|
142
163
|
for (let i = 0; i < nodes.length; i++) {
|
|
143
164
|
const el = nodes[i];
|
|
144
|
-
if (!el || isBeeChrome(el) || isSkippedTag(el)) continue;
|
|
165
|
+
if (!el || isBeeChrome(el) || isSkippedTag(el) || isReportNoise(el)) continue;
|
|
145
166
|
const measured = measureViewportOverflow(el);
|
|
146
167
|
if (measured) overflow.push(measured);
|
|
147
168
|
const findings = [];
|
|
148
169
|
pushNegativeMargins(el, findings);
|
|
149
170
|
pushInvisible(el, findings);
|
|
150
|
-
pushBlockers(el, findings);
|
|
171
|
+
pushBlockers(el, findings, true);
|
|
151
172
|
pushImage(el, findings);
|
|
152
|
-
pushStacking(el, findings);
|
|
173
|
+
pushStacking(el, findings, true);
|
|
153
174
|
pushImportant(el, this.#rules, findings);
|
|
154
175
|
for (let j = 0; j < findings.length; j++) {
|
|
155
176
|
rows.push({ el, selector: cssPath(el), ...findings[j] });
|
|
@@ -245,7 +266,9 @@ export class CSSAdapter {
|
|
|
245
266
|
for (let i = 0; i < rows.length; i++) {
|
|
246
267
|
const row = rows[i];
|
|
247
268
|
if (describeCause(row.kind, row.selector)) {
|
|
248
|
-
|
|
269
|
+
// No cap here: MAX_SNAPSHOT applies after the severity sort, so an info row
|
|
270
|
+
// can never push a real error out.
|
|
271
|
+
causes = addCause(causes, { kind: row.kind, path: row.selector, el: row.el }, Infinity);
|
|
249
272
|
} else {
|
|
250
273
|
plain.push(row);
|
|
251
274
|
}
|
|
@@ -319,6 +342,7 @@ export class CSSAdapter {
|
|
|
319
342
|
#disable() {
|
|
320
343
|
if (!this.#active) return;
|
|
321
344
|
this.#active = false;
|
|
345
|
+
this.#hasPointer = false;
|
|
322
346
|
if (typeof window !== 'undefined') {
|
|
323
347
|
window.removeEventListener('mousemove', this.#onMove, true);
|
|
324
348
|
window.removeEventListener('keydown', this.#onKey);
|
|
@@ -340,11 +364,16 @@ export class CSSAdapter {
|
|
|
340
364
|
this.core?.publishDetail([]);
|
|
341
365
|
this.core?.publishIssues([]);
|
|
342
366
|
this.core?.sendData('state', { source: this.source, key: 'css.hunt', value: 'off' });
|
|
367
|
+
this.core?.sendData('state', { source: this.source, key: 'css.target', value: '—' });
|
|
368
|
+
this.core?.sendData('state', { source: this.source, key: 'css.hits', value: '0' });
|
|
369
|
+
this.core?.sendData('state', { source: this.source, key: 'css.pin', value: 'off' });
|
|
343
370
|
this.core?.sendData('log', { source: this.source, message: 'CSS hunt off.' });
|
|
344
371
|
}
|
|
345
372
|
|
|
346
373
|
#handleKey(event) {
|
|
347
374
|
if (!this.#active || event.repeat) return;
|
|
375
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
376
|
+
if (this.core && this.core.visible === false) return;
|
|
348
377
|
if (isTypingTarget(event.target)) return;
|
|
349
378
|
if (event.code === this.pinKey) {
|
|
350
379
|
event.preventDefault();
|
|
@@ -377,22 +406,23 @@ export class CSSAdapter {
|
|
|
377
406
|
|
|
378
407
|
#inspect(x, y) {
|
|
379
408
|
const el = pickElement(x, y);
|
|
380
|
-
placeBox(this.#hoverBox, el);
|
|
409
|
+
placeBox(this.#hoverBox, el, this.#boxesVisible());
|
|
381
410
|
if (this.#panelLock) return;
|
|
382
411
|
if (el === this.#lastEl) return;
|
|
383
412
|
this.#lastEl = el;
|
|
384
413
|
if (!el) {
|
|
385
414
|
this.core?.publishDetail([]);
|
|
386
415
|
this.core?.sendData('state', { source: this.source, key: 'css.target', value: '—' });
|
|
416
|
+
this.core?.sendData('state', { source: this.source, key: 'css.hits', value: '0' });
|
|
387
417
|
return;
|
|
388
418
|
}
|
|
389
419
|
const findings = [];
|
|
390
420
|
pushImportant(el, this.#rules, findings);
|
|
391
421
|
pushOverflow(el, findings);
|
|
392
422
|
pushInvisible(el, findings);
|
|
393
|
-
pushBlockers(el, findings);
|
|
423
|
+
pushBlockers(el, findings, true);
|
|
394
424
|
pushImage(el, findings);
|
|
395
|
-
pushStacking(el, findings);
|
|
425
|
+
pushStacking(el, findings, true);
|
|
396
426
|
const path = cssPath(el);
|
|
397
427
|
this.core?.sendData('state', { source: this.source, key: 'css.target', value: path });
|
|
398
428
|
this.core?.sendData('state', {
|
|
@@ -419,7 +449,7 @@ export class CSSAdapter {
|
|
|
419
449
|
if (this.#panelLock) {
|
|
420
450
|
this.#panelLock = false;
|
|
421
451
|
this.#pinned = null;
|
|
422
|
-
placeBox(this.#pinBox, null);
|
|
452
|
+
placeBox(this.#pinBox, null, this.#boxesVisible());
|
|
423
453
|
this.core?.sendData('state', { source: this.source, key: 'css.pin', value: 'off' });
|
|
424
454
|
this.core?.sendData('log', { source: this.source, message: 'CSS pin off.' });
|
|
425
455
|
this.#lastEl = null;
|
|
@@ -504,15 +534,16 @@ export class CSSAdapter {
|
|
|
504
534
|
|
|
505
535
|
#pin(el) {
|
|
506
536
|
this.#pinned = el;
|
|
507
|
-
placeBox(this.#pinBox, el);
|
|
537
|
+
placeBox(this.#pinBox, el, this.#boxesVisible());
|
|
508
538
|
if (el && typeof el.scrollIntoView === 'function') {
|
|
509
539
|
el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });
|
|
510
540
|
}
|
|
511
541
|
}
|
|
512
542
|
|
|
513
543
|
#placeBoxes() {
|
|
514
|
-
|
|
515
|
-
placeBox(this.#
|
|
544
|
+
const visible = this.#boxesVisible();
|
|
545
|
+
placeBox(this.#hoverBox, this.#lastEl, visible);
|
|
546
|
+
placeBox(this.#pinBox, this.#pinned, visible);
|
|
516
547
|
}
|
|
517
548
|
|
|
518
549
|
#mountBoxes() {
|
|
@@ -538,6 +569,8 @@ function collectImportantRules() {
|
|
|
538
569
|
const sheets = document.styleSheets;
|
|
539
570
|
for (let i = 0; i < sheets.length; i++) {
|
|
540
571
|
const sheet = sheets[i];
|
|
572
|
+
if (sheet.disabled === true) continue;
|
|
573
|
+
if (sheet.media && sheet.media.mediaText && !mediaMatches(sheet.media.mediaText)) continue;
|
|
541
574
|
let list = null;
|
|
542
575
|
try {
|
|
543
576
|
list = sheet.cssRules;
|
|
@@ -550,21 +583,21 @@ function collectImportantRules() {
|
|
|
550
583
|
return { rules, unreadable };
|
|
551
584
|
}
|
|
552
585
|
|
|
586
|
+
function mediaMatches(mediaText) {
|
|
587
|
+
try {
|
|
588
|
+
return typeof matchMedia === 'function' ? matchMedia(mediaText).matches : true;
|
|
589
|
+
} catch {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
553
594
|
function walkRules(list, source, into) {
|
|
554
595
|
if (!list) return;
|
|
555
596
|
for (let i = 0; i < list.length; i++) {
|
|
556
597
|
const rule = list[i];
|
|
557
598
|
if (!rule) continue;
|
|
558
599
|
if (rule.type === 4 && rule.media) {
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
active = typeof matchMedia === 'function'
|
|
562
|
-
? matchMedia(rule.media.mediaText).matches
|
|
563
|
-
: true;
|
|
564
|
-
} catch {
|
|
565
|
-
active = true;
|
|
566
|
-
}
|
|
567
|
-
if (active) walkRules(rule.cssRules, source, into);
|
|
600
|
+
if (mediaMatches(rule.media.mediaText)) walkRules(rule.cssRules, source, into);
|
|
568
601
|
continue;
|
|
569
602
|
}
|
|
570
603
|
if (rule.type === 1 && rule.selectorText && rule.style) {
|
|
@@ -745,7 +778,9 @@ function pushInvisible(el, findings) {
|
|
|
745
778
|
findings.push(issue('invisible text (opacity 0)', ISSUE_RANK.invisible, 30));
|
|
746
779
|
}
|
|
747
780
|
const color = parseColor(style.color);
|
|
748
|
-
|
|
781
|
+
// Unknown syntax (oklch, lab, color(), color-mix): nothing to compare, nothing to report.
|
|
782
|
+
if (!color) return;
|
|
783
|
+
if (color.a === 0) {
|
|
749
784
|
findings.push(issue('invisible text (transparent color)', ISSUE_RANK.invisible, 30));
|
|
750
785
|
return;
|
|
751
786
|
}
|
|
@@ -1096,7 +1131,7 @@ function pickElement(x, y) {
|
|
|
1096
1131
|
const stack = document.elementsFromPoint(x, y);
|
|
1097
1132
|
for (let i = 0; i < stack.length; i++) {
|
|
1098
1133
|
const el = stack[i];
|
|
1099
|
-
if (!el || isBeeChrome(el)) continue;
|
|
1134
|
+
if (!el || isBeeChrome(el) || isReportNoise(el)) continue;
|
|
1100
1135
|
if (el === document.documentElement || el === document.body) continue;
|
|
1101
1136
|
return el;
|
|
1102
1137
|
}
|
|
@@ -1154,12 +1189,16 @@ function hasOwnText(el) {
|
|
|
1154
1189
|
function cssPath(el) {
|
|
1155
1190
|
if (!el || !el.tagName) return 'node';
|
|
1156
1191
|
const tag = el.tagName.toLowerCase();
|
|
1157
|
-
if (el.id) return `${tag}#${el.id}`;
|
|
1192
|
+
if (el.id) return `${tag}#${cssEscape(el.id)}`;
|
|
1158
1193
|
const classes = el.classList ? [...el.classList].slice(0, 2) : [];
|
|
1159
|
-
if (classes.length) return `${tag}.${classes.join('.')}`;
|
|
1194
|
+
if (classes.length) return `${tag}.${classes.map(cssEscape).join('.')}`;
|
|
1160
1195
|
return tag;
|
|
1161
1196
|
}
|
|
1162
1197
|
|
|
1198
|
+
function cssEscape(value) {
|
|
1199
|
+
return String(value).replace(/[^\w-]/g, '\\$&');
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1163
1202
|
function isSkippedTag(el) {
|
|
1164
1203
|
const tag = el.tagName;
|
|
1165
1204
|
return tag === 'SCRIPT' || tag === 'STYLE' || tag === 'LINK' || tag === 'META';
|
|
@@ -1179,7 +1218,10 @@ function touchesSheet(record) {
|
|
|
1179
1218
|
const name = record.target?.nodeName;
|
|
1180
1219
|
return name === 'STYLE' || name === 'LINK';
|
|
1181
1220
|
}
|
|
1182
|
-
|
|
1221
|
+
// Only what was added or removed: the target's whole subtree always holds some <style> or <link>.
|
|
1222
|
+
// A <style> that receives new text is the one target worth checking, and that check is O(1).
|
|
1223
|
+
if (record.target?.nodeName === 'STYLE') return true;
|
|
1224
|
+
const nodes = [];
|
|
1183
1225
|
if (record.addedNodes) {
|
|
1184
1226
|
for (let i = 0; i < record.addedNodes.length; i++) nodes.push(record.addedNodes[i]);
|
|
1185
1227
|
}
|
|
@@ -1220,9 +1262,9 @@ function makeBox(kind, border, fill) {
|
|
|
1220
1262
|
return node;
|
|
1221
1263
|
}
|
|
1222
1264
|
|
|
1223
|
-
function placeBox(node, el) {
|
|
1265
|
+
function placeBox(node, el, visible = true) {
|
|
1224
1266
|
if (!node) return;
|
|
1225
|
-
if (!el || typeof el.getBoundingClientRect !== 'function' || isBeeChrome(el)) {
|
|
1267
|
+
if (visible === false || !el || typeof el.getBoundingClientRect !== 'function' || isBeeChrome(el)) {
|
|
1226
1268
|
node.style.display = 'none';
|
|
1227
1269
|
return;
|
|
1228
1270
|
}
|
|
@@ -520,6 +520,7 @@ function isBeeChrome(node) {
|
|
|
520
520
|
if (tag === 'BEE-LADYBUG-OVERLAY') return true;
|
|
521
521
|
if (el.hasAttribute?.('data-bee-ladybug-dom')) return true;
|
|
522
522
|
if (el.hasAttribute?.('data-bee-ladybug-canvas')) return true;
|
|
523
|
+
if (el.hasAttribute?.('data-bee-ladybug-css')) return true;
|
|
523
524
|
el = el.parentElement;
|
|
524
525
|
}
|
|
525
526
|
return false;
|
|
@@ -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.4';
|
|
14
14
|
|
|
15
15
|
export const CORE_DEFAULTS = Object.freeze({
|
|
16
16
|
toggleKey: 'F2',
|