bee-ladybug 0.5.3 → 0.5.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/README.MD +1 -0
- package/package.json +1 -1
- package/src/adapters/CSSAdapter.js +130 -13
- package/src/core/BeeLadybugCore.js +1 -1
- package/src/core/UIOverlay.js +1 -0
package/README.MD
CHANGED
|
@@ -199,6 +199,7 @@ css.attach();
|
|
|
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
|
+
- un antenato che crea uno stacking context (`transform`, `opacity` sotto 1, `filter`, oppure `position` non static con `z-index`). I figli si raggruppano sulla causa: `div.hero creates stacking context (transform) - z-index of 3 children is trapped`. `will-change: transform` o `opacity` è una riga grigia, con la nota sullo `z-index` sulla stessa riga
|
|
202
203
|
- `<img>` con rapporto del file diverso di oltre il 5% dal riquadro disegnato (`getBoundingClientRect`), solo se `object-fit` calcolato è `fill` (il default)
|
|
203
204
|
|
|
204
205
|
`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.
|
package/package.json
CHANGED
|
@@ -143,6 +143,7 @@ export class CSSAdapter {
|
|
|
143
143
|
pushInvisible(el, findings);
|
|
144
144
|
pushBlockers(el, findings);
|
|
145
145
|
pushImage(el, findings);
|
|
146
|
+
pushStacking(el, findings);
|
|
146
147
|
pushImportant(el, this.#rules, findings);
|
|
147
148
|
for (let j = 0; j < findings.length; j++) {
|
|
148
149
|
rows.push({ el, selector: cssPath(el), ...findings[j] });
|
|
@@ -293,6 +294,7 @@ export class CSSAdapter {
|
|
|
293
294
|
pushInvisible(el, findings);
|
|
294
295
|
pushBlockers(el, findings);
|
|
295
296
|
pushImage(el, findings);
|
|
297
|
+
pushStacking(el, findings);
|
|
296
298
|
const path = cssPath(el);
|
|
297
299
|
this.core?.sendData('state', { source: this.source, key: 'css.target', value: path });
|
|
298
300
|
this.core?.sendData('state', {
|
|
@@ -340,7 +342,7 @@ export class CSSAdapter {
|
|
|
340
342
|
for (let i = 0; i < findings.length; i++) {
|
|
341
343
|
const hit = findings[i];
|
|
342
344
|
const text = hit.text || hit.kind || '';
|
|
343
|
-
if (!text || text.startsWith('unreadable sheet:')) continue;
|
|
345
|
+
if (!text || text.startsWith('unreadable sheet:') || hit.level === 'info') continue;
|
|
344
346
|
if (!rememberIssue(this.#issueLog, `${path}\0${text}`, now, this.issueWindow)) continue;
|
|
345
347
|
const level = hit.level === 'error' ? 'error' : 'warn';
|
|
346
348
|
this.core?.sendData(level, {
|
|
@@ -380,20 +382,25 @@ export class CSSAdapter {
|
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
#publishIssues() {
|
|
383
|
-
const bugs =
|
|
384
|
-
level: 'error',
|
|
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
|
-
}));
|
|
385
|
+
const bugs = [];
|
|
392
386
|
const notes = this.#notes.map((note) => ({
|
|
393
387
|
level: 'info',
|
|
394
388
|
key: note.text,
|
|
395
389
|
text: note.text
|
|
396
390
|
}));
|
|
391
|
+
for (let i = 0; i < this.#issues.length; i++) {
|
|
392
|
+
const entry = this.#issues[i];
|
|
393
|
+
const item = {
|
|
394
|
+
key: entry.key,
|
|
395
|
+
text: formatCause(entry),
|
|
396
|
+
targets: entry.targets.map((target) => ({
|
|
397
|
+
text: target.text,
|
|
398
|
+
onSelect: () => this.#pin(target.el)
|
|
399
|
+
}))
|
|
400
|
+
};
|
|
401
|
+
if (entry.level === 'info') notes.push({ ...item, level: 'info' });
|
|
402
|
+
else bugs.push({ ...item, level: 'error' });
|
|
403
|
+
}
|
|
397
404
|
this.core?.publishIssues(bugs.concat(notes));
|
|
398
405
|
}
|
|
399
406
|
|
|
@@ -598,9 +605,11 @@ function pushNegativeMargins(el, findings) {
|
|
|
598
605
|
const ISSUE_RANK = Object.freeze({
|
|
599
606
|
overflow: 0,
|
|
600
607
|
blocker: 1,
|
|
608
|
+
stack: 1,
|
|
601
609
|
invisible: 2,
|
|
602
610
|
image: 3,
|
|
603
|
-
important: 4
|
|
611
|
+
important: 4,
|
|
612
|
+
hint: 6
|
|
604
613
|
});
|
|
605
614
|
|
|
606
615
|
/**
|
|
@@ -729,12 +738,32 @@ export function pushStickyIssue(list, entry, max = 10) {
|
|
|
729
738
|
|
|
730
739
|
export function describeCause(kind, path) {
|
|
731
740
|
if (kind === 'overflows viewport') {
|
|
732
|
-
return { key: `overflow\0${path}`, type: 'overflow', cause: path };
|
|
741
|
+
return { key: `overflow\0${path}`, type: 'overflow', cause: path, reason: '', level: 'error' };
|
|
733
742
|
}
|
|
734
743
|
const prefix = 'click blocked by ';
|
|
735
744
|
if (typeof kind === 'string' && kind.startsWith(prefix)) {
|
|
736
745
|
const cause = kind.slice(prefix.length);
|
|
737
|
-
return { key: `block\0${cause}`, type: 'block', cause };
|
|
746
|
+
return { key: `block\0${cause}`, type: 'block', cause, reason: '', level: 'error' };
|
|
747
|
+
}
|
|
748
|
+
const stack = typeof kind === 'string' ? kind.match(/^stacking context \(([^)]+)\) by (.+)$/) : null;
|
|
749
|
+
if (stack) {
|
|
750
|
+
return {
|
|
751
|
+
key: `stack\0${stack[2]}\0${stack[1]}`,
|
|
752
|
+
type: 'stack',
|
|
753
|
+
cause: stack[2],
|
|
754
|
+
reason: stack[1],
|
|
755
|
+
level: 'error'
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
const hint = typeof kind === 'string' ? kind.match(/^will-change (.+?) by (.+)$/) : null;
|
|
759
|
+
if (hint) {
|
|
760
|
+
return {
|
|
761
|
+
key: `will\0${hint[2]}\0${hint[1]}`,
|
|
762
|
+
type: 'stack-hint',
|
|
763
|
+
cause: hint[2],
|
|
764
|
+
reason: hint[1],
|
|
765
|
+
level: 'info'
|
|
766
|
+
};
|
|
738
767
|
}
|
|
739
768
|
return null;
|
|
740
769
|
}
|
|
@@ -760,6 +789,8 @@ export function addCause(list, entry, max = 10) {
|
|
|
760
789
|
key: described.key,
|
|
761
790
|
type: described.type,
|
|
762
791
|
cause: described.cause,
|
|
792
|
+
reason: described.reason || '',
|
|
793
|
+
level: described.level || 'error',
|
|
763
794
|
targets
|
|
764
795
|
});
|
|
765
796
|
return rest.slice(0, max);
|
|
@@ -771,10 +802,96 @@ export function formatCause(entry) {
|
|
|
771
802
|
const noun = count === 1 ? 'element' : 'elements';
|
|
772
803
|
return `${entry.cause} blocks clicks on ${count} ${noun}`;
|
|
773
804
|
}
|
|
805
|
+
if (entry.type === 'stack') {
|
|
806
|
+
const noun = count === 1 ? 'child' : 'children';
|
|
807
|
+
return `${entry.cause} creates stacking context (${entry.reason}) - z-index of ${count} ${noun} is trapped`;
|
|
808
|
+
}
|
|
809
|
+
if (entry.type === 'stack-hint') {
|
|
810
|
+
const noun = count === 1 ? 'child' : 'children';
|
|
811
|
+
return `${entry.cause} will-change: ${entry.reason} — z-index of ${count} ${noun} stays in this layer`;
|
|
812
|
+
}
|
|
774
813
|
if (count <= 1) return `${entry.cause} overflows viewport`;
|
|
775
814
|
return `${entry.cause} overflows viewport · ${count} elements`;
|
|
776
815
|
}
|
|
777
816
|
|
|
817
|
+
/**
|
|
818
|
+
* Properties that create a stacking context. `will-change` for transform or
|
|
819
|
+
* opacity is only a hint: it may promote a layer, it is not reported as a bug.
|
|
820
|
+
* @param {{ transform?: string, opacity?: string, filter?: string, position?: string, zIndex?: string, willChange?: string, getPropertyValue?: (name: string) => string }} style
|
|
821
|
+
*/
|
|
822
|
+
export function stackingReasons(style) {
|
|
823
|
+
const read = (prop, camel) => {
|
|
824
|
+
if (style && typeof style.getPropertyValue === 'function') {
|
|
825
|
+
const raw = style.getPropertyValue(prop);
|
|
826
|
+
if (raw) return String(raw).trim();
|
|
827
|
+
}
|
|
828
|
+
if (style && style[camel] != null && style[camel] !== '') return String(style[camel]).trim();
|
|
829
|
+
return '';
|
|
830
|
+
};
|
|
831
|
+
const bugs = [];
|
|
832
|
+
const transform = read('transform', 'transform');
|
|
833
|
+
if (transform && transform !== 'none') bugs.push('transform');
|
|
834
|
+
const opacity = parseFloat(read('opacity', 'opacity'));
|
|
835
|
+
if (Number.isFinite(opacity) && opacity < 1) bugs.push('opacity');
|
|
836
|
+
const filter = read('filter', 'filter');
|
|
837
|
+
if (filter && filter !== 'none') bugs.push('filter');
|
|
838
|
+
const position = read('position', 'position') || 'static';
|
|
839
|
+
const zIndex = read('z-index', 'zIndex') || 'auto';
|
|
840
|
+
if (position !== 'static' && zIndex !== 'auto') bugs.push('z-index');
|
|
841
|
+
const hint = willChangeHint(read('will-change', 'willChange'));
|
|
842
|
+
return { bugs, hint };
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function willChangeHint(value) {
|
|
846
|
+
if (!value || value === 'auto') return '';
|
|
847
|
+
const parts = value.split(',').map((part) => part.trim().toLowerCase());
|
|
848
|
+
const hit = [];
|
|
849
|
+
for (let i = 0; i < parts.length; i++) {
|
|
850
|
+
if ((parts[i] === 'transform' || parts[i] === 'opacity') && !hit.includes(parts[i])) hit.push(parts[i]);
|
|
851
|
+
}
|
|
852
|
+
return hit.join(', ');
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function pushStacking(el, findings) {
|
|
856
|
+
if (!el || typeof getComputedStyle !== 'function' || !el.parentElement) return;
|
|
857
|
+
const own = getComputedStyle(el);
|
|
858
|
+
const ownZ = own.zIndex || own.getPropertyValue?.('z-index') || 'auto';
|
|
859
|
+
if (!ownZ || ownZ === 'auto') return;
|
|
860
|
+
let node = el.parentElement;
|
|
861
|
+
while (node && node.nodeType === 1) {
|
|
862
|
+
if (isBeeChrome(node)) return;
|
|
863
|
+
const found = stackingReasons(getComputedStyle(node));
|
|
864
|
+
const ancestor = cssPath(node);
|
|
865
|
+
if (found.bugs.length) {
|
|
866
|
+
const reason = found.bugs[0];
|
|
867
|
+
findings.push({
|
|
868
|
+
level: 'error',
|
|
869
|
+
rank: ISSUE_RANK.stack,
|
|
870
|
+
score: 35,
|
|
871
|
+
kind: `stacking context (${reason}) by ${ancestor}`,
|
|
872
|
+
text: `z-index trapped by ${ancestor} (${reason})`
|
|
873
|
+
});
|
|
874
|
+
if (found.hint) findings.push(stackHint(ancestor, found.hint));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (found.hint) {
|
|
878
|
+
findings.push(stackHint(ancestor, found.hint));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
node = node.parentElement;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function stackHint(ancestor, hint) {
|
|
886
|
+
return {
|
|
887
|
+
level: 'info',
|
|
888
|
+
rank: ISSUE_RANK.hint,
|
|
889
|
+
score: 1,
|
|
890
|
+
kind: `will-change ${hint} by ${ancestor}`,
|
|
891
|
+
text: `will-change: ${hint} on ${ancestor}`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
778
895
|
function sameTarget(a, b) {
|
|
779
896
|
if (a.el && b.el) return a.el === b.el;
|
|
780
897
|
return a.text === b.text;
|
|
@@ -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.4';
|
|
13
13
|
|
|
14
14
|
export const CORE_DEFAULTS = Object.freeze({
|
|
15
15
|
toggleKey: 'F2',
|
package/src/core/UIOverlay.js
CHANGED
|
@@ -358,6 +358,7 @@ const STYLES = `
|
|
|
358
358
|
}
|
|
359
359
|
.bl-detail button[data-level="warn"] { color: #ffd36a; }
|
|
360
360
|
.bl-detail button[data-level="error"] { color: #ff6b7d; }
|
|
361
|
+
.bl-detail button[data-level="info"] { color: #8b91a3; }
|
|
361
362
|
.bl-detail button:hover { background: rgba(255, 255, 255, 0.04); }
|
|
362
363
|
.bl-adapter {
|
|
363
364
|
appearance: none;
|