Repository navigation
Expand file tree
/
Copy pathinputModality.ts
More file actions
85 lines (81 loc) Β· 2.81 KB
/
Copy pathinputModality.ts
File metadata and controls
85 lines (81 loc) Β· 2.81 KB
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* Focus rings for keyboard focus only.
*
* A row or a button clicked with the mouse takes focus without a ring.
* But the browser paints `:focus-visible` on it the moment any key is
* pressed β it reads the key as keyboard navigation β and the ring then
* stays until the next click. Escape did it (#859), and so did every
* shortcut of the desktop around the app (#866): under Hyprland most of
* them go through the Super key, so Super+Q to open a terminal left a
* green frame on the last track clicked, and digits, letters and F-keys
* did the same.
*
* So the modality is decided when focus **arrives**: an element focused
* right after a pointer press is marked `data-pointer-focus`, and
* `app.css` takes the ring off a marked element whatever is pressed
* afterwards. Focus that arrives from the keyboard (Tab, the arrows of a
* list) carries no mark and keeps its ring, which is how a keyboard user
* knows where they are.
*
* Imported once, for its listeners, from `main.tsx`.
*/
type InputModality = "pointer" | "keyboard";
let last: InputModality = "pointer";
/** Keys that navigate nothing: modifiers, and Escape. */
const IGNORED_KEYS = new Set([
"Escape",
"Shift",
"Control",
"Alt",
"AltGraph",
"Meta",
"OS",
"Super",
"Hyper",
"CapsLock",
]);
const MARK = "pointerFocus";
if (typeof window !== "undefined") {
window.addEventListener("pointerdown", () => (last = "pointer"), true);
window.addEventListener(
"keydown",
(event) => {
if (!IGNORED_KEYS.has(event.key)) last = "keyboard";
},
true,
);
document.addEventListener(
"focusin",
(event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) return;
if (last === "pointer") {
target.dataset[MARK] = "";
} else if (event.relatedTarget !== null) {
// Focus moved here from another element by a key: a keyboard
// focus, ring and all. With no `relatedTarget` it came back from
// outside the page β the window was minimised or left β onto
// the element that had it, which keeps whatever mark it had: the
// last key pressed before leaving says nothing about how it was
// focused.
delete target.dataset[MARK];
}
},
true,
);
document.addEventListener(
"focusout",
(event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) return;
// Only once focus has really gone elsewhere in the page. When the
// whole window loses focus, the element stays the active one and
// gets it back on return; dropping the mark here is what put the
// ring on a clicked row after minimising and restoring (#866).
setTimeout(() => {
if (document.activeElement !== target) delete target.dataset[MARK];
}, 0);
},
true,
);
}