diff --git a/packages/core/src/events/FocusManager.test.ts b/packages/core/src/events/FocusManager.test.ts index b735f6a3..fb18aada 100644 --- a/packages/core/src/events/FocusManager.test.ts +++ b/packages/core/src/events/FocusManager.test.ts @@ -264,4 +264,21 @@ describe('FocusManager Re-entrancy', () => { // After re-entrant focusNext, should end up on 'c' expect(fm.currentId).toBe('c'); }); + + it('registering a new focusable that sorts before current does not change current focus', () => { + const fm = new FocusManager(); + // A (10), B (20) + fm.register(makeWidget('a', 10, true)); + fm.register(makeWidget('b', 20, true)); + + // Focus B + fm.focusWidget('b'); + expect(fm.currentId).toBe('b'); + + // Register C with lower tabIndex that sorts before existing items + fm.register(makeWidget('c', 5, true)); + + // Observable behavior: focused id must remain 'b' + expect(fm.currentId).toBe('b'); + }); }); \ No newline at end of file diff --git a/packages/core/src/events/FocusManager.ts b/packages/core/src/events/FocusManager.ts index 805edd3a..bd496bf4 100644 --- a/packages/core/src/events/FocusManager.ts +++ b/packages/core/src/events/FocusManager.ts @@ -93,9 +93,20 @@ export class FocusManager { * they are not lost when App has not yet subscribed to them. */ register(focusable: Focusable): void { + // Preserve currently focused id so that sorting the master list + // does not accidentally change which widget is focused. + const prevFocusedId = this.currentId; + this._focusables.push(focusable); this._focusables.sort((a, b) => a.tabIndex - b.tabIndex); + // If there was a previously focused widget, relocate _currentIndex + // to point to the same widget after the sort. + if (prevFocusedId) { + const newIdx = this._focusables.findIndex(f => f.id === prevFocusedId); + if (newIdx >= 0) this._currentIndex = newIdx; + } + // Auto-focus the first widget if nothing is focused if (this._currentIndex < 0 && focusable.focusable) { this._currentIndex = this._focusables.indexOf(focusable);