summaryrefslogtreecommitdiff
path: root/source/features/cycle-lists-with-keyboard-shortcuts.tsx
blob: 212ff2139c12a4c1972f0a220c3ed418de96c0c4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import select from 'select-dom';
import delegate from 'delegate-it';
import * as pageDetect from 'github-url-detection';

import features from '.';

function init(): void {
	let selectableItems: HTMLElement[] = [];
	let lastSelectableItemIndex: number;

	function populateSelectableItems(): void {
		requestAnimationFrame(() => {
			selectableItems = select.all([
				'.js-active-navigation-container .select-menu-list:not([hidden]) .js-navigation-item:not([hidden])', // All selectable items in the current tab
				'.js-active-navigation-container .js-new-label-modal:not(.d-none) .js-navigation-item', // "Create label" button, when selecting labels
				'.js-active-navigation-container a.js-navigation-item.js-label-options' // "Edit labels" link, when selecting labels
			]);

			lastSelectableItemIndex = selectableItems.length - 1;
		});
	}

	function performSwapFocus(event: KeyboardEvent, from: number, to: number): void {
		event.preventDefault();
		event.stopPropagation();

		selectableItems[from].classList.remove('navigation-focus');
		selectableItems[from].setAttribute('aria-selected', 'false');
		selectableItems[to].classList.add('navigation-focus');
		selectableItems[to].setAttribute('aria-selected', 'true');

		selectableItems[to].scrollIntoView({
			block: 'nearest'
		});
	}

	function handleKeyDown(event: KeyboardEvent): void {
		if (selectableItems.length === 0) { // Empty projects and milestones list
			return;
		}

		if (selectableItems[0].matches('.navigation-focus') && event.key === 'ArrowUp') {
			performSwapFocus(event, 0, lastSelectableItemIndex);
		} else if (selectableItems[lastSelectableItemIndex].matches('.navigation-focus') && event.key === 'ArrowDown') {
			performSwapFocus(event, lastSelectableItemIndex, 0);
		}
	}

	// Input fields for projects and milestones are added dynamically to the page
	// GitHub triggers events on the document element for us, which can be used to detect new input elements
	delegate(document, '.js-filterable-field', 'filterable:change', populateSelectableItems);
	delegate(document, '.js-filterable-field', 'keydown', handleKeyDown);
}

void features.add(__filebasename, {
	include: [
		pageDetect.isPRConversation,
		pageDetect.isIssue,
		pageDetect.isCompare
	],
	init
});