mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
MAESTRO: Fix ToC blocking file content scroll
Replace backdrop-based click-outside detection with useClickOutside hook. The previous fixed backdrop div intercepted all pointer events including wheel events, preventing file content from scrolling while ToC was open. Now wheel events over file content scroll the content, while wheel events over the ToC scroll the ToC list.
This commit is contained in:
@@ -753,9 +753,9 @@ print("world")
|
||||
expect(screen.getByText('Contents')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('stops wheel event propagation on TOC overlay to isolate scrolling', () => {
|
||||
it('closes TOC when clicking outside of it', async () => {
|
||||
const markdownWithHeadings = '# Heading 1\n## Heading 2\n## Heading 3';
|
||||
render(
|
||||
const { container } = render(
|
||||
<FilePreview
|
||||
{...defaultProps}
|
||||
file={{ name: 'doc.md', content: markdownWithHeadings, path: '/test/doc.md' }}
|
||||
@@ -767,23 +767,18 @@ print("world")
|
||||
const tocButton = screen.getByTitle('Table of Contents');
|
||||
fireEvent.click(tocButton);
|
||||
|
||||
// Find the TOC container by looking for the element with the heading entries
|
||||
const tocContainer = screen.getByText('Contents').closest('div')?.parentElement;
|
||||
expect(tocContainer).toBeInTheDocument();
|
||||
// Verify TOC is open
|
||||
expect(screen.getByText('Contents')).toBeInTheDocument();
|
||||
|
||||
// Create a wheel event and verify it doesn't propagate
|
||||
const wheelEvent = new WheelEvent('wheel', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
deltaY: 100,
|
||||
});
|
||||
const stopPropagationSpy = vi.spyOn(wheelEvent, 'stopPropagation');
|
||||
// Wait for the delay in useClickOutside hook
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Dispatch the wheel event on the TOC container
|
||||
tocContainer?.dispatchEvent(wheelEvent);
|
||||
// Click outside the TOC (on the main container)
|
||||
const mainContainer = container.firstChild as HTMLElement;
|
||||
fireEvent.mouseDown(mainContainer);
|
||||
|
||||
// The onWheel handler should have called stopPropagation
|
||||
expect(stopPropagationSpy).toHaveBeenCalled();
|
||||
// TOC should be closed
|
||||
expect(screen.queryByText('Contents')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -689,6 +689,8 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
|
||||
const matchElementsRef = useRef<HTMLElement[]>([]);
|
||||
const cancelButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const scrollSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const tocButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const tocOverlayRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Expose focus method to parent via ref
|
||||
useImperativeHandle(
|
||||
@@ -1158,6 +1160,11 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
|
||||
// Disable click-outside in tab mode - tabs should only close via explicit user action
|
||||
useClickOutside(containerRef, handleEscapeRequest, !!file && !isTabMode, { delay: true });
|
||||
|
||||
// Click outside ToC overlay to dismiss (exclude both overlay and the toggle button)
|
||||
// Use delay to prevent the click that opened it from immediately closing it
|
||||
const closeTocOverlay = useCallback(() => setShowTocOverlay(false), []);
|
||||
useClickOutside<HTMLElement>([tocOverlayRef, tocButtonRef], closeTocOverlay, showTocOverlay, { delay: true });
|
||||
|
||||
// Keep search input focused when search is open
|
||||
useEffect(() => {
|
||||
if (searchOpen && searchInputRef.current) {
|
||||
@@ -2231,6 +2238,7 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
|
||||
<>
|
||||
{/* Floating TOC Button */}
|
||||
<button
|
||||
ref={tocButtonRef}
|
||||
onClick={() => setShowTocOverlay(!showTocOverlay)}
|
||||
className="absolute bottom-4 right-4 p-2.5 rounded-full shadow-lg transition-all duration-200 hover:scale-105 z-10"
|
||||
style={{
|
||||
@@ -2243,19 +2251,14 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
|
||||
<List className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* TOC Overlay */}
|
||||
{/* TOC Overlay - click outside handled by useClickOutside hook */}
|
||||
{showTocOverlay && (
|
||||
<>
|
||||
{/* Click-outside backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 z-15"
|
||||
onClick={() => setShowTocOverlay(false)}
|
||||
/>
|
||||
<div
|
||||
className="absolute bottom-16 right-4 rounded-lg shadow-xl overflow-hidden z-20 animate-in fade-in slide-in-from-bottom-2 duration-200 flex flex-col"
|
||||
style={{
|
||||
backgroundColor: theme.colors.bgSidebar,
|
||||
border: `1px solid ${theme.colors.border}`,
|
||||
<div
|
||||
ref={tocOverlayRef}
|
||||
className="absolute bottom-16 right-4 rounded-lg shadow-xl overflow-hidden z-20 animate-in fade-in slide-in-from-bottom-2 duration-200 flex flex-col"
|
||||
style={{
|
||||
backgroundColor: theme.colors.bgSidebar,
|
||||
border: `1px solid ${theme.colors.border}`,
|
||||
maxHeight: 'calc(70vh - 80px)',
|
||||
minWidth: '200px',
|
||||
maxWidth: '350px',
|
||||
@@ -2363,7 +2366,6 @@ export const FilePreview = forwardRef<FilePreviewHandle, FilePreviewProps>(funct
|
||||
<span>Bottom</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user