@@ -9,7 +9,6 @@ import Component from '@glimmer/component';
99import { tracked } from ' @glimmer/tracking' ;
1010
1111import { dropTask } from ' ember-concurrency' ;
12- import { modifier } from ' ember-modifier' ;
1312import { velcro } from ' ember-velcro' ;
1413import { isEqual , omit } from ' lodash' ;
1514
@@ -79,7 +78,6 @@ export default class Overlays extends Component<OverlaySignature> {
7978 <div
8079 class ={{this .overlayClassName }}
8180 {{velcro renderedCard.element middleware =( array this . offset) }}
82- {{this .revealWhenPositioned }}
8381 style ={{renderedCard.overlayZIndexStyle }}
8482 data-test-card-overlay
8583 ...attributes
@@ -128,83 +126,57 @@ export default class Overlays extends Component<OverlaySignature> {
128126 protected offset = {
129127 name: ' offset' ,
130128 fn : (state : MiddlewareState ) => {
131- let { elements, rects } = state ;
129+ let { elements } = state ;
132130 let { floating, reference } = elements ;
133- let { width, height } = reference .getBoundingClientRect ();
131+ let refRect = reference .getBoundingClientRect ();
134132
135- floating .style .width = width + ' px' ;
136- floating .style .height = height + ' px' ;
133+ floating .style .width = refRect . width + ' px' ;
134+ floating .style .height = refRect . height + ' px' ;
137135 floating .style .position = ' absolute' ;
138136 // Mirror the underlying card's corner radius so any decorative
139137 // outline / box-shadow on the overlay follows the same curve.
140138 if (reference instanceof Element ) {
141139 floating .style .borderRadius =
142140 window .getComputedStyle (reference ).borderRadius ;
143141 }
142+
143+ // Position the overlay from the live reference rect relative to the
144+ // floating element's own offset parent, rather than floating-ui's
145+ // `rects.reference`. floating-ui's first one-or-two computePosition calls
146+ // omit the offset parent's offset (they return the reference in viewport
147+ // coordinates and only subtract the offset parent a frame later), so
148+ // trusting `rects.reference` makes the overlay — and everything riding it
149+ // (the type-label tab, the select chip, the menu, the outline) — paint
150+ // one frame off and visibly jump into place on first appearance.
151+ // Computing it ourselves from the current rects is correct on the very
152+ // first frame. We recover the offset parent's scale the same way the
153+ // Adorn label positioner does (the test runner scales `#ember-testing`),
154+ // and convert the viewport anchor into the offset parent's local space.
155+ let offsetParent = floating .offsetParent as HTMLElement | null ;
156+ let parentRect = offsetParent
157+ ? offsetParent .getBoundingClientRect ()
158+ : new DOMRect (0 , 0 , window .innerWidth , window .innerHeight );
159+ let scaleX =
160+ offsetParent && offsetParent .offsetWidth > 0
161+ ? parentRect .width / offsetParent .offsetWidth
162+ : 1 ;
163+ let scaleY =
164+ offsetParent && offsetParent .offsetHeight > 0
165+ ? parentRect .height / offsetParent .offsetHeight
166+ : 1 ;
167+ if (! Number .isFinite (scaleX ) || scaleX === 0 ) {
168+ scaleX = 1 ;
169+ }
170+ if (! Number .isFinite (scaleY ) || scaleY === 0 ) {
171+ scaleY = 1 ;
172+ }
144173 return {
145- x: rects . reference . x ,
146- y: rects . reference . y ,
174+ x: ( refRect . left - parentRect . left ) / scaleX ,
175+ y: ( refRect . top - parentRect . top ) / scaleY ,
147176 };
148177 },
149178 };
150179
151- // Whether to keep the overlay hidden (opacity 0) until its velcro position
152- // has settled, to avoid the first-frame jump. Off by default (consumers
153- // without visible chrome — spec-preview, playground — don't need it);
154- // OperatorModeOverlays overrides it.
155- protected get hideUntilPositioned(): boolean {
156- return false ;
157- }
158-
159- // floating-ui's first one-or-two computePosition calls return the reference
160- // in viewport coordinates rather than offset-parent-relative ones (it omits
161- // the offset parent's offset for ~1 frame, then corrects), so the overlay —
162- // and everything riding it (the type-label tab, the select chip, the menu,
163- // the outline) — paints one frame off and visibly jumps into place. Hold the
164- // overlay at opacity 0 until its measured rect has been unchanged for two
165- // consecutive frames, then reveal. We watch the actual geometry rather than
166- // count frames or compare middleware outputs, because the wrong position can
167- // repeat across calls and the correction lands a frame after the first paint;
168- // only the rect going stable is a reliable "done moving" signal. Opacity (not
169- // visibility) keeps the overlay's action buttons hit-testable throughout.
170- protected revealWhenPositioned = modifier ((element : HTMLElement ) => {
171- if (! this .hideUntilPositioned ) {
172- return undefined ;
173- }
174- element .style .opacity = ' 0' ;
175- let lastTop: number | undefined ;
176- let lastLeft: number | undefined ;
177- let stableFrames = 0 ;
178- let elapsedFrames = 0 ;
179- let raf = 0 ;
180- let tick = () => {
181- let { top, left } = element .getBoundingClientRect ();
182- if (top === lastTop && left === lastLeft ) {
183- stableFrames += 1 ;
184- } else {
185- stableFrames = 0 ;
186- lastTop = top ;
187- lastLeft = left ;
188- }
189- elapsedFrames += 1 ;
190- // Reveal once the rect has settled, with a safety cap so a perpetually
191- // animating reference can't keep the overlay hidden forever.
192- if (stableFrames >= 2 || elapsedFrames > 30 ) {
193- element .style .opacity = ' ' ;
194- return ;
195- }
196- // eslint-disable-next-line @cardstack/boxel/no-raf-for-state
197- raf = requestAnimationFrame (tick );
198- };
199- // eslint-disable-next-line @cardstack/boxel/no-raf-for-state
200- raf = requestAnimationFrame (tick );
201- return () => {
202- if (raf ) {
203- cancelAnimationFrame (raf );
204- }
205- };
206- });
207-
208180 // Since we put absolutely positined overlays containing operator mode actions on top of the rendered cards,
209181 // we are running into a problem where the overlays are interfering with scrolling of the container that holds the rendered cards.
210182 // That means scrolling stops when the cursor gets over the overlay, which is a bug. We solved this problem by disabling pointer
0 commit comments