Skip to content

Commit 63412bb

Browse files
lukemeliaclaude
andcommitted
Hide the Adorn label tab until its position settles
The teal type-label tab is rendered inside the velcro-positioned overlay that is its offset parent. For the first frame or two after the tab appears on hover, that overlay hasn't finished being placed, so positionAdornLabel computes the tab's `top`/`left` against a parent rect that hasn't settled — painting the tab ~30px too high before floating-ui's layout-shift observer re-fires and drops it to the correct spot. (Confirmed via frame-by-frame capture: offsetHeight is constant, so it isn't label-height settling; the written `top` itself changes over the first ~2 frames as the offset parent moves into place.) Keep the tab hidden and reveal it one frame after the most recent position write — rescheduled on every update — so it only becomes visible once it has stopped moving. The settle takes ~1–2 frames, so the reveal delay is imperceptible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ce60b49 commit 63412bb

1 file changed

Lines changed: 30 additions & 1 deletion

File tree

packages/host/app/modifiers/position-adorn-label.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ export function makePositionAdornLabel(
6464
label.style.top = '0';
6565
label.style.left = '0';
6666

67+
// Keep the label hidden until its position has settled. The label is
68+
// rendered inside the velcro-positioned overlay that is its offset parent
69+
// (e.g. operator-mode's actions-overlay); for the first frame or two after
70+
// the label appears that overlay is still being placed, so `update()`
71+
// computes `top`/`left` against a parent rect that hasn't settled and the
72+
// tab paints at the wrong spot before floating-ui's layout-shift observer
73+
// re-fires and corrects it. Reveal one frame after the most recent
74+
// position write — rescheduled on every update — so the tab only becomes
75+
// visible once it has stopped moving.
76+
label.style.visibility = 'hidden';
77+
let revealHandle: number | undefined;
78+
let scheduleReveal = () => {
79+
if (revealHandle !== undefined) {
80+
cancelAnimationFrame(revealHandle);
81+
}
82+
// eslint-disable-next-line @cardstack/boxel/no-raf-for-state
83+
revealHandle = requestAnimationFrame(() => {
84+
revealHandle = undefined;
85+
label.style.visibility = 'visible';
86+
});
87+
};
88+
6789
let update = () => {
6890
label.style.maxWidth = 'none';
6991
let labelWidth = label.scrollWidth;
@@ -151,8 +173,15 @@ export function makePositionAdornLabel(
151173
}
152174
label.style.left = (anchorLeftX - parentRect.left) / scaleX + 'px';
153175
label.style.top = (anchorTopY - parentRect.top) / scaleY + 'px';
176+
scheduleReveal();
154177
};
155178

156-
return autoUpdate(cardEl, label, update);
179+
let cleanup = autoUpdate(cardEl, label, update);
180+
return () => {
181+
if (revealHandle !== undefined) {
182+
cancelAnimationFrame(revealHandle);
183+
}
184+
cleanup();
185+
};
157186
});
158187
}

0 commit comments

Comments
 (0)