File selector files move around too much (vibe-kanban) (#122)
* I've simplified the dropdown positioning to be much more stable and consistent. The key changes: 1. **Removed complex cursor-based positioning** - Instead of trying to calculate exact character positions, the dropdown now appears consistently below (or above) the textarea 2. **Fixed reference point** - Always positions relative to the textarea edges rather than cursor position 3. **Simpler logic** - Just checks if there's enough space below, otherwise positions above 4. **Consistent spacing** - Uses a 4px gap between textarea and dropdown This approach eliminates the positioning inconsistencies while keeping the dropdown visually connected to the textarea where the user is typing. * Perfect! Now the dropdown positioning accounts for the actual content height when positioning above the textarea. The key changes: 1. **Calculate actual height** - Based on the number of filtered results (`filteredFiles.length * itemHeight + dropdownPadding`) 2. **Use actual height for positioning** - When positioning above, uses `actualHeight` instead of `maxDropdownHeight` to eliminate gaps 3. **Configurable constants** - Added `itemHeight` and `dropdownPadding` that can be adjusted based on your actual item styling This ensures the dropdown appears right above the textarea without blank space, regardless of how many results are shown. * Fixed! Now the dropdown positioning correctly uses `searchResults.length` instead of the undefined `filteredFiles` variable. The dropdown will now properly calculate its actual height based on the number of search results and position itself without gaps when appearing above the textarea. * I've increased the `itemHeight` to 60px to better account for the actual rendered height of each item (which has `px-3 py-2` padding plus two lines of text), and reduced the `dropdownPadding` to 8px to match the `py-1` class. This should eliminate the gap when the dropdown is positioned above the textarea. * Now I'm using a ref-based approach that measures the actual rendered height of the dropdown and repositions it accordingly. The key changes: 1. **Real height measurement** - Uses `dropdownRef.current?.getBoundingClientRect().height` to get the actual rendered height 2. **Dynamic repositioning** - Added a `useEffect` that runs when the dropdown content changes to reposition it 3. **Eliminated guesswork** - No more trying to predict the height with calculations This should eliminate the gap completely since it uses the actual rendered dimensions rather than estimates. * prettier
This commit is contained in:
committed by
GitHub
parent
e4188ed949
commit
62380777c9
@@ -169,97 +169,74 @@ export function FileSearchTextarea({
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// Calculate dropdown position relative to viewport
|
||||
// Calculate dropdown position relative to textarea (simpler, more stable approach)
|
||||
const getDropdownPosition = () => {
|
||||
if (!textareaRef.current || atSymbolPosition === -1)
|
||||
return { top: 0, left: 0, maxHeight: 240 };
|
||||
if (!textareaRef.current) return { top: 0, left: 0, maxHeight: 240 };
|
||||
|
||||
const textareaRect = textareaRef.current.getBoundingClientRect();
|
||||
const textBeforeAt = value.slice(0, atSymbolPosition);
|
||||
const lines = textBeforeAt.split('\n');
|
||||
const currentLine = lines.length - 1;
|
||||
const charInLine = lines[lines.length - 1].length;
|
||||
|
||||
// More accurate calculation using computed styles
|
||||
const computedStyle = window.getComputedStyle(textareaRef.current);
|
||||
const lineHeight = parseInt(computedStyle.lineHeight) || 20;
|
||||
const fontSize = parseInt(computedStyle.fontSize) || 14;
|
||||
const charWidth = fontSize * 0.6; // Approximate character width
|
||||
const paddingLeft = parseInt(computedStyle.paddingLeft) || 12;
|
||||
const paddingTop = parseInt(computedStyle.paddingTop) || 8;
|
||||
|
||||
// Position relative to textarea
|
||||
const relativeTop = paddingTop + currentLine * lineHeight + lineHeight;
|
||||
const relativeLeft = paddingLeft + charWidth * charInLine;
|
||||
|
||||
// Convert to viewport coordinates
|
||||
const viewportTop = textareaRect.top + relativeTop;
|
||||
const viewportLeft = textareaRect.left + relativeLeft;
|
||||
|
||||
// Dropdown dimensions
|
||||
const dropdownWidth = 256; // min-w-64 = 256px
|
||||
const maxDropdownHeight = 320;
|
||||
const minDropdownHeight = 120;
|
||||
const maxDropdownHeight = 400; // Increased to show more results without scrolling
|
||||
|
||||
let finalTop = viewportTop;
|
||||
let finalLeft = viewportLeft;
|
||||
// Position dropdown below the textarea by default
|
||||
let finalTop = textareaRect.bottom + 4; // 4px gap
|
||||
let finalLeft = textareaRect.left;
|
||||
let maxHeight = maxDropdownHeight;
|
||||
|
||||
// Prevent going off the right edge
|
||||
if (viewportLeft + dropdownWidth > window.innerWidth - 16) {
|
||||
// Ensure dropdown doesn't go off the right edge
|
||||
if (finalLeft + dropdownWidth > window.innerWidth - 16) {
|
||||
finalLeft = window.innerWidth - dropdownWidth - 16;
|
||||
}
|
||||
|
||||
// Prevent going off the left edge
|
||||
// Ensure dropdown doesn't go off the left edge
|
||||
if (finalLeft < 16) {
|
||||
finalLeft = 16;
|
||||
}
|
||||
|
||||
// Smart positioning: avoid clipping by positioning above when needed
|
||||
const availableSpaceBelow = window.innerHeight - viewportTop - 32;
|
||||
const availableSpaceAbove =
|
||||
textareaRect.top + currentLine * lineHeight - 32;
|
||||
// Calculate available space below and above textarea
|
||||
const availableSpaceBelow = window.innerHeight - textareaRect.bottom - 32;
|
||||
const availableSpaceAbove = textareaRect.top - 32;
|
||||
|
||||
// Check if dropdown would be clipped at bottom - if so, try positioning above
|
||||
const wouldBeClippedBelow = availableSpaceBelow < maxDropdownHeight;
|
||||
const hasEnoughSpaceAbove = availableSpaceAbove >= maxDropdownHeight;
|
||||
|
||||
if (wouldBeClippedBelow && hasEnoughSpaceAbove) {
|
||||
// Position above the cursor line with full height
|
||||
finalTop =
|
||||
textareaRect.top +
|
||||
paddingTop +
|
||||
currentLine * lineHeight -
|
||||
maxDropdownHeight;
|
||||
maxHeight = maxDropdownHeight;
|
||||
} else if (
|
||||
wouldBeClippedBelow &&
|
||||
// If not enough space below, position above
|
||||
if (
|
||||
availableSpaceBelow < minDropdownHeight &&
|
||||
availableSpaceAbove > availableSpaceBelow
|
||||
) {
|
||||
// Position above but with reduced height if not enough space
|
||||
finalTop =
|
||||
textareaRect.top +
|
||||
paddingTop +
|
||||
currentLine * lineHeight -
|
||||
availableSpaceAbove;
|
||||
maxHeight = Math.max(availableSpaceAbove, minDropdownHeight);
|
||||
// Get actual height from rendered dropdown
|
||||
const actualHeight =
|
||||
dropdownRef.current?.getBoundingClientRect().height ||
|
||||
minDropdownHeight;
|
||||
finalTop = textareaRect.top - actualHeight - 4;
|
||||
maxHeight = Math.min(
|
||||
maxDropdownHeight,
|
||||
Math.max(availableSpaceAbove, minDropdownHeight)
|
||||
);
|
||||
} else {
|
||||
// Position below the cursor line
|
||||
// Position below with available space
|
||||
maxHeight = Math.min(
|
||||
maxDropdownHeight,
|
||||
Math.max(availableSpaceBelow, minDropdownHeight)
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure minimum height
|
||||
if (maxHeight < minDropdownHeight) {
|
||||
maxHeight = minDropdownHeight;
|
||||
finalTop = Math.max(16, window.innerHeight - minDropdownHeight - 16);
|
||||
}
|
||||
|
||||
return { top: finalTop, left: finalLeft, maxHeight };
|
||||
};
|
||||
|
||||
// Use effect to reposition when dropdown content changes
|
||||
useEffect(() => {
|
||||
if (showDropdown && dropdownRef.current) {
|
||||
// Small delay to ensure content is rendered
|
||||
setTimeout(() => {
|
||||
const newPosition = getDropdownPosition();
|
||||
if (dropdownRef.current) {
|
||||
dropdownRef.current.style.top = `${newPosition.top}px`;
|
||||
dropdownRef.current.style.left = `${newPosition.left}px`;
|
||||
dropdownRef.current.style.maxHeight = `${newPosition.maxHeight}px`;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}, [searchResults.length, showDropdown]);
|
||||
|
||||
const dropdownPosition = getDropdownPosition();
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user