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 inputRef = useRef<HTMLInputElement>(null);
|
||||||
const fade = useScrollFade();
|
const fade = useScrollFade();
|
||||||
|
|
||||||
const { refs, floatingStyles, context } = useFloating({
|
const { refs, floatingStyles, context, isPositioned } = useFloating({
|
||||||
open,
|
open,
|
||||||
onOpenChange: setOpen,
|
onOpenChange: setOpen,
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
@@ -62,12 +62,10 @@ export default function MultiSelectChips({
|
|||||||
shift({ padding: 8 }),
|
shift({ padding: 8 }),
|
||||||
size({
|
size({
|
||||||
padding: 8,
|
padding: 8,
|
||||||
apply({ rects, elements, availableHeight }) {
|
apply({ rects, elements }) {
|
||||||
// Match the field width and cap to the space below (or 320px, whichever is smaller).
|
// Match the field width (with a sane minimum for a very narrow rail); the inner scroll box
|
||||||
Object.assign(elements.floating.style, {
|
// owns the height cap (max-h-64).
|
||||||
width: `${rects.reference.width}px`,
|
elements.floating.style.width = `${Math.max(rects.reference.width, 192)}px`;
|
||||||
maxHeight: `${Math.min(availableHeight, 320)}px`,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -166,15 +164,22 @@ export default function MultiSelectChips({
|
|||||||
<FloatingPortal>
|
<FloatingPortal>
|
||||||
<div
|
<div
|
||||||
ref={refs.setFloating}
|
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()}
|
{...getFloatingProps()}
|
||||||
aria-label={label}
|
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
|
<div
|
||||||
ref={fade.ref}
|
ref={fade.ref}
|
||||||
style={fade.style}
|
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 ? (
|
{filtered.length === 0 ? (
|
||||||
<p className="px-2 py-1.5 text-xs text-muted">
|
<p className="px-2 py-1.5 text-xs text-muted">
|
||||||
|
|||||||
Reference in New Issue
Block a user