fix(e5): stop the filter dropdown flashing at the origin before it positions
The combi-input popover flashed in the top-left corner for a frame, then jumped into place. Two causes, both confirmed with a per-frame capture in the browser: 1. The panel painted before floating-ui computed a position — gated its visibility on `isPositioned` (hidden until positioned), same as PopoverSurface. 2. The bigger one: the `popIn` entrance animation animates `transform` (scale/translateY), and it sat on the SAME element floating-ui positions via `transform` — so during the 0.16s animation the keyframes OVERRODE the translate(x,y) and the panel rendered at the origin. Moved the glass styling + popIn onto an inner child, leaving the positioned element's transform untouched. Frame capture after the fix: frame 0 hidden, first visible frame already at translate(82,474) — never visible at the origin. Also gave the size middleware a min width for a narrow rail.
This commit is contained in:
@@ -51,7 +51,7 @@ export default function MultiSelectChips({
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const fade = useScrollFade();
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
const { refs, floatingStyles, context, isPositioned } = useFloating({
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
placement: "bottom-start",
|
||||
@@ -62,12 +62,10 @@ export default function MultiSelectChips({
|
||||
shift({ padding: 8 }),
|
||||
size({
|
||||
padding: 8,
|
||||
apply({ rects, elements, availableHeight }) {
|
||||
// Match the field width and cap to the space below (or 320px, whichever is smaller).
|
||||
Object.assign(elements.floating.style, {
|
||||
width: `${rects.reference.width}px`,
|
||||
maxHeight: `${Math.min(availableHeight, 320)}px`,
|
||||
});
|
||||
apply({ rects, elements }) {
|
||||
// Match the field width (with a sane minimum for a very narrow rail); the inner scroll box
|
||||
// owns the height cap (max-h-64).
|
||||
elements.floating.style.width = `${Math.max(rects.reference.width, 192)}px`;
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -166,15 +164,22 @@ export default function MultiSelectChips({
|
||||
<FloatingPortal>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
// Keep the panel hidden until floating-ui has computed a real position — otherwise the first
|
||||
// frame paints it at the container's top-left before autoUpdate runs. (isPositioned guard,
|
||||
// same as PopoverSurface.)
|
||||
style={{ ...floatingStyles, visibility: isPositioned ? "visible" : "hidden" }}
|
||||
{...getFloatingProps()}
|
||||
aria-label={label}
|
||||
className="glass-menu z-popover min-w-48 rounded-xl p-1.5 shadow-lg animate-[popIn_0.16s_ease]"
|
||||
className="z-popover"
|
||||
>
|
||||
{/* The entrance animation + glass styling live ONE level in: popIn animates `transform`
|
||||
(scale/translateY), which on the positioned element itself would OVERRIDE floating-ui's
|
||||
transform-based position and flash the panel at the origin for the animation's duration.
|
||||
Keeping it on a child leaves the outer element's position transform untouched. */}
|
||||
<div
|
||||
ref={fade.ref}
|
||||
style={fade.style}
|
||||
className="max-h-64 overflow-y-auto no-scrollbar"
|
||||
className="glass-menu max-h-64 w-full overflow-y-auto rounded-xl p-1.5 no-scrollbar shadow-lg animate-[popIn_0.16s_ease]"
|
||||
>
|
||||
{filtered.length === 0 ? (
|
||||
<p className="px-2 py-1.5 text-xs text-muted">
|
||||
|
||||
Reference in New Issue
Block a user