mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
Fix tab bar scroll to keep active tab fully visible after rename
When a tab is renamed (manually or via auto-generation), its width may change. Added activeTabName as a dependency to the scroll-into-view effect so the tab bar auto-scrolls to keep the active tab (including close button) fully visible after name changes.
This commit is contained in:
@@ -1581,6 +1581,56 @@ describe('TabBar', () => {
|
|||||||
|
|
||||||
rafSpy.mockRestore();
|
rafSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('scrolls active tab into view when its name changes (e.g., after auto-generation)', async () => {
|
||||||
|
// Mock requestAnimationFrame
|
||||||
|
const rafSpy = vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
|
||||||
|
cb(0);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
createTab({ id: 'tab-1', name: null }), // Tab without name initially
|
||||||
|
createTab({ id: 'tab-2', name: 'Tab 2' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const { rerender, container } = render(
|
||||||
|
<TabBar
|
||||||
|
tabs={tabs}
|
||||||
|
activeTabId="tab-1"
|
||||||
|
theme={mockTheme}
|
||||||
|
onTabSelect={mockOnTabSelect}
|
||||||
|
onTabClose={mockOnTabClose}
|
||||||
|
onNewTab={mockOnNewTab}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate the active tab's name being updated (e.g., auto-generated name)
|
||||||
|
// This should trigger a scroll to ensure the now-wider tab is still visible
|
||||||
|
const updatedTabs = [
|
||||||
|
createTab({ id: 'tab-1', name: 'A Much Longer Auto-Generated Tab Name' }),
|
||||||
|
createTab({ id: 'tab-2', name: 'Tab 2' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<TabBar
|
||||||
|
tabs={updatedTabs}
|
||||||
|
activeTabId="tab-1"
|
||||||
|
theme={mockTheme}
|
||||||
|
onTabSelect={mockOnTabSelect}
|
||||||
|
onTabClose={mockOnTabClose}
|
||||||
|
onNewTab={mockOnNewTab}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// The scroll behavior uses getBoundingClientRect which returns 0s in JSDOM,
|
||||||
|
// so we just verify the effect runs without error and the tab renders with new name
|
||||||
|
const activeTab = container.querySelector('[data-tab-id="tab-1"]');
|
||||||
|
expect(activeTab).toBeTruthy();
|
||||||
|
expect(screen.getByText('A Much Longer Auto-Generated Tab Name')).toBeTruthy();
|
||||||
|
|
||||||
|
rafSpy.mockRestore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('styling', () => {
|
describe('styling', () => {
|
||||||
|
|||||||
@@ -1599,7 +1599,14 @@ function TabBarInner({
|
|||||||
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||||
|
|
||||||
// Ensure the active tab is fully visible (including close button) when activeTabId or activeFileTabId changes, or filter is toggled
|
// Get active tab's name to trigger scroll when it changes (e.g., after auto-generated name)
|
||||||
|
const activeTab = tabs.find((t) => t.id === activeTabId);
|
||||||
|
const activeTabName = activeTab?.name ?? null;
|
||||||
|
|
||||||
|
// Ensure the active tab is fully visible (including close button) when:
|
||||||
|
// - activeTabId or activeFileTabId changes (new tab selected)
|
||||||
|
// - activeTabName changes (tab renamed, so width may have changed)
|
||||||
|
// - filter is toggled
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Double requestAnimationFrame ensures the DOM has fully updated after React's state changes
|
// Double requestAnimationFrame ensures the DOM has fully updated after React's state changes
|
||||||
// First rAF: React has committed changes but browser hasn't painted yet
|
// First rAF: React has committed changes but browser hasn't painted yet
|
||||||
@@ -1634,7 +1641,7 @@ function TabBarInner({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [activeTabId, activeFileTabId, showUnreadOnly]);
|
}, [activeTabId, activeFileTabId, activeTabName, showUnreadOnly]);
|
||||||
|
|
||||||
// Can always close tabs - closing the last one creates a fresh new tab
|
// Can always close tabs - closing the last one creates a fresh new tab
|
||||||
const canClose = true;
|
const canClose = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user