summaryrefslogtreecommitdiff
path: root/source/features/cycle-lists-with-keyboard-shortcuts.tsx
blob: 7834b2398ce3dce03b04254a509d74a0fb36aa88 (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
63
64
65
66
67
68
import select from 'select-dom';
import delegate from 'delegate-it';
import features from '../libs/features';

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
			].join(','));

			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', event => {
		populateSelectableItems();

		const input = event.delegateTarget as HTMLElement;
		input.addEventListener('keydown', handleKeyDown);
	});
}

features.add({
	id: __featureName__,
	description: 'Allows the `↑` and `↓` keys to cycle "popover lists" (labels, milestones, etc).',
	screenshot: 'https://user-images.githubusercontent.com/37769974/59158786-6fd2c400-8add-11e9-9db1-db80186fa6ea.gif',
	load: features.onAjaxedPages,
	include: [
		features.isPRConversation,
		features.isIssue,
		features.isCompare
	],
	init
});