mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +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();
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
||||
@@ -1599,7 +1599,14 @@ function TabBarInner({
|
||||
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||
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(() => {
|
||||
// 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
|
||||
@@ -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
|
||||
const canClose = true;
|
||||
|
||||
Reference in New Issue
Block a user