fix(ui): a pin stack taller than the part was clipped by the preview

The ReSpeaker's left side carries 13 labels: at the minimum spacing that is
208px against a 190px body, so the column ran past both ends and the first
and last labels were cut off by the preview's clip. The gutters now grow to
whatever the labels actually occupy - the box follows the content.
This commit is contained in:
David Montero Crespo 2026-08-03 23:20:49 +02:00
parent cbc74d8409
commit 4be8ddd6c3
2 changed files with 38 additions and 4 deletions

View File

@ -193,6 +193,17 @@ describe('layoutInspectorPins — inset columns (the ReSpeaker Lite case)', () =
it('never puts a column end-pin in the top or bottom gutter', () => {
expect(r.pins.some((p) => p.edge === 'top' || p.edge === 'bottom')).toBe(false);
});
it('grows the box so a stack taller than the body is not clipped', () => {
// 13 labels on the left at >=16px need more room than the scaled body,
// so the first and last used to be cut off by the preview's clip.
const top = -r.padTop;
const bottom = r.artHeight + r.padBottom;
for (const p of r.pins) {
expect(p.labelY).toBeGreaterThanOrEqual(top);
expect(p.labelY).toBeLessThanOrEqual(bottom);
}
});
});
describe('signalKindOf', () => {

View File

@ -287,14 +287,37 @@ export function layoutInspectorPins(
}
const has = (e: PinEdge) => classified.some((c) => c.edge === e);
let padLeft = has('left') ? SIDE_GUTTER : 0;
let padRight = has('right') ? SIDE_GUTTER : 0;
let padTop = has('top') ? CAP_GUTTER : 0;
let padBottom = has('bottom') ? CAP_GUTTER : 0;
// A stack taller than the art must not be clipped. A ReSpeaker's left side
// carries 13 labels: at the minimum spacing that is 208 px against a 190 px
// body, so the column necessarily runs past both ends and the first and
// last labels were cut off. Grow the gutters to whatever the labels
// actually occupy — the box follows the content, not the other way round.
const HALF_LINE = 7; // half a label's line box, plus a hair of margin
for (const p of laid) {
if (!p) continue;
if (p.edge === 'left' || p.edge === 'right') {
padTop = Math.max(padTop, HALF_LINE - p.labelY);
padBottom = Math.max(padBottom, p.labelY + HALF_LINE - H);
} else {
// Vertical text on the caps: its extent along x is the line box.
padLeft = Math.max(padLeft, HALF_LINE - p.labelX);
padRight = Math.max(padRight, p.labelX + HALF_LINE - W);
}
}
return {
scale,
artWidth: W,
artHeight: H,
pins: laid,
padLeft: has('left') ? SIDE_GUTTER : 0,
padRight: has('right') ? SIDE_GUTTER : 0,
padTop: has('top') ? CAP_GUTTER : 0,
padBottom: has('bottom') ? CAP_GUTTER : 0,
padLeft: Math.max(0, Math.round(padLeft)),
padRight: Math.max(0, Math.round(padRight)),
padTop: Math.max(0, Math.round(padTop)),
padBottom: Math.max(0, Math.round(padBottom)),
};
}