Skip to content

Commit 95e0035

Browse files
chrywCopilot
andcommitted
sessions: fix hover reverse navigation
Traverse rich-hover controls and row actions symmetrically with Shift+Tab, and preserve GitHub's distinct Duplicate issue terminology. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 26d4a47 commit 95e0035

4 files changed

Lines changed: 44 additions & 22 deletions

File tree

src/vs/platform/actionWidget/browser/actionList.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,21 +2198,6 @@ export class ActionListWidget<T> extends Disposable {
21982198
};
21992199
}
22002200

2201-
/**
2202-
* The toolbar index Shift+Tab should return to when leaving the panel. Skips a trailing
2203-
* removal action (appended after the item's own {@link IActionListItem.toolbarActions})
2204-
* so the destructive Remove control isn't the panel's silent return target.
2205-
*/
2206-
private _lastPrimaryToolbarActionIndex(toolbar: ActionBar): number {
2207-
const items = toolbar.viewItems;
2208-
for (let i = items.length - 1; i >= 0; i--) {
2209-
if (items[i].action.id !== removeToolbarActionId) {
2210-
return i;
2211-
}
2212-
}
2213-
return items.length - 1;
2214-
}
2215-
22162201
private _focusFirstTabThroughPanelControl(element: IActionListItem<T>, row: HTMLElement): void {
22172202
const controls = this._getTabThroughPanelControls(element, row);
22182203
if (controls.toolbar?.length()) {
@@ -2262,12 +2247,16 @@ export class ActionListWidget<T> extends Disposable {
22622247
let target: HTMLElement | undefined;
22632248
if (event.shiftKey) {
22642249
if (inPanel) {
2265-
if (controls.toolbar?.length()) {
2250+
const panelControlIndex = controls.panelControls.indexOf(activeElement);
2251+
if (panelControlIndex > 0) {
2252+
target = controls.panelControls[panelControlIndex - 1];
2253+
} else if (controls.toolbar?.length()) {
22662254
dom.EventHelper.stop(event, true);
2267-
controls.toolbar.focus(this._lastPrimaryToolbarActionIndex(controls.toolbar));
2255+
controls.toolbar.focus(controls.toolbar.length() - 1);
22682256
return;
2257+
} else {
2258+
target = this._list.getHTMLElement();
22692259
}
2270-
target = this._list.getHTMLElement();
22712260
} else if (controls.toolbar?.isFocused()) {
22722261
const toolbarIndex = controls.toolbar.viewItems.findIndex((_, actionIndex) => controls.toolbar?.isFocused(actionIndex));
22732262
if (toolbarIndex > 0) {

src/vs/platform/actionWidget/test/browser/actionList.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,10 @@ suite('ActionListWidget', () => {
12931293
const enterDefaultPreserved = press('Enter');
12941294
const spaceDefaultPreserved = press(' ');
12951295
press('Tab', true);
1296+
const backToReference = focusState();
1297+
press('Tab', true);
1298+
const backToRepository = focusState();
1299+
press('Tab', true);
12961300
const backToCopy = focusState();
12971301
press('Tab', true);
12981302
const backToList = focusState();
@@ -1312,6 +1316,8 @@ suite('ActionListWidget', () => {
13121316
reference,
13131317
branch,
13141318
panelActivation: { bubbledPanelActivationKeys, enterDefaultPreserved, spaceDefaultPreserved },
1319+
backToReference,
1320+
backToRepository,
13151321
backToCopy,
13161322
backToList,
13171323
nextItem,
@@ -1327,6 +1333,8 @@ suite('ActionListWidget', () => {
13271333
reference: { location: 'panel', label: '#one' },
13281334
branch: { location: 'panel', label: 'Copy branch one' },
13291335
panelActivation: { bubbledPanelActivationKeys: [], enterDefaultPreserved: true, spaceDefaultPreserved: true },
1336+
backToReference: { location: 'panel', label: '#one' },
1337+
backToRepository: { location: 'panel', label: 'repo-one' },
13301338
backToCopy: { location: 'toolbar', label: 'Copy one' },
13311339
backToList: { location: 'list', label: 'Action Widget' },
13321340
nextItem: {
@@ -1337,7 +1345,7 @@ suite('ActionListWidget', () => {
13371345
});
13381346
});
13391347

1340-
test('Shift+Tab from the panel returns to the last non-removal toolbar action', () => {
1348+
test('Shift+Tab traverses the panel and toolbar controls in reverse order', () => {
13411349
const createPanel = () => {
13421350
const panel = document.createElement('div');
13431351
const control = document.createElement('a');
@@ -1377,8 +1385,13 @@ suite('ActionListWidget', () => {
13771385
press('Tab'); // Copy -> Remove
13781386
press('Tab'); // Remove -> panel link
13791387
press('Tab', true); // panel link -> Shift+Tab back into the toolbar
1388+
const firstReverseTarget = focusedToolbarLabel();
1389+
press('Tab', true); // Remove -> Copy
13801390

1381-
assert.strictEqual(focusedToolbarLabel(), 'Copy');
1391+
assert.deepStrictEqual({ firstReverseTarget, secondReverseTarget: focusedToolbarLabel() }, {
1392+
firstReverseTarget: 'Remove',
1393+
secondReverseTarget: 'Copy',
1394+
});
13821395
});
13831396

13841397
test('rebuilding the items in place re-measures only when the row count changed', () => {

src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ suite('SessionChatInputToolbar', () => {
264264
openerService,
265265
sessionsService,
266266
).flatMap(section => section.entries)[0];
267+
const duplicateIssueEntry = buildSessionIssueSections(
268+
[{ ref: issueRef, issue: { ...issue, stateReason: GitHubIssueStateReason.Duplicate } }],
269+
undefined,
270+
commandService,
271+
clipboardService,
272+
openerService,
273+
sessionsService,
274+
).flatMap(section => section.entries)[0];
267275
const unresolvedIssueEntry = buildSessionIssueSections(
268276
[{ ref: issueRef, issue: undefined }],
269277
undefined,
@@ -284,6 +292,7 @@ suite('SessionChatInputToolbar', () => {
284292
const pullRequestHover = await renderHover(pullRequestEntry);
285293
const issueHover = await renderHover(issueEntry);
286294
const activeIssueHover = await renderHover(activeIssueEntry);
295+
const duplicateIssueHover = await renderHover(duplicateIssueEntry);
287296
const pullRequestDropdownHover = renderDropdownHover(pullRequestEntry);
288297
const issueDropdownHover = renderDropdownHover(issueEntry);
289298
pullRequestHover?.querySelectorAll<HTMLButtonElement>('.sessions-pr-hover-branch').forEach(branch => branch.click());
@@ -373,6 +382,10 @@ suite('SessionChatInputToolbar', () => {
373382
status: activeIssueHover?.querySelector('.sessions-issue-hover-status')?.textContent,
374383
date: activeIssueHover?.querySelector('.sessions-issue-hover-date')?.textContent,
375384
},
385+
duplicateIssue: {
386+
status: duplicateIssueHover?.querySelector('.sessions-issue-hover-status')?.textContent,
387+
statusKind: duplicateIssueHover?.querySelector<HTMLElement>('.sessions-issue-hover-status')?.dataset.state,
388+
},
376389
}, {
377390
pullRequest: {
378391
label: 'Restore rich pill hovers',
@@ -478,6 +491,10 @@ suite('SessionChatInputToolbar', () => {
478491
status: 'Open',
479492
date: 'on Sep 3',
480493
},
494+
duplicateIssue: {
495+
status: 'Duplicate',
496+
statusKind: 'duplicate',
497+
},
481498
});
482499
});
483500

src/vs/sessions/contrib/github/browser/issueHover.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,14 @@ function appendHoverLink(container: HTMLElement, className: string, href: string
9494
return link;
9595
}
9696

97-
function getIssueStatus(issue: IGitHubIssue): { readonly kind: 'open' | 'closed' | 'notPlanned'; readonly label: string } {
97+
function getIssueStatus(issue: IGitHubIssue): { readonly kind: 'open' | 'closed' | 'notPlanned' | 'duplicate'; readonly label: string } {
9898
if (issue.state === GitHubIssueState.Open) {
9999
return { kind: 'open', label: localize('agentSessions.issueHover.open', "Open") };
100100
}
101-
if (issue.stateReason === GitHubIssueStateReason.NotPlanned || issue.stateReason === GitHubIssueStateReason.Duplicate) {
101+
if (issue.stateReason === GitHubIssueStateReason.Duplicate) {
102+
return { kind: 'duplicate', label: localize('agentSessions.issueHover.duplicate', "Duplicate") };
103+
}
104+
if (issue.stateReason === GitHubIssueStateReason.NotPlanned) {
102105
return { kind: 'notPlanned', label: localize('agentSessions.issueHover.notPlanned', "Not planned") };
103106
}
104107
return { kind: 'closed', label: localize('agentSessions.issueHover.closed', "Closed") };

0 commit comments

Comments
 (0)