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
86
87
88
89
90
91
92
// Document key listeners. Run: `cargo test -p zdc-runtime`
//
// Its own module rather than four lines in `dom.js`, for the reason
// `list.js`, `markup.js` and `foreign.js` are their own modules: a program
// that writes no `on key` must not download this (spec §16.3.1). It imports
// `signal.js` and nothing else — it needs a listener and a focus question,
// not a node to render into, so a program whose only DOM work is a shortcut
// does not link the renderer.
//
// # What this file is actually for
//
// `document.addEventListener('keydown', …)` is a strictly larger capability
// than a listener on one element: it receives keystrokes aimed at *every*
// element on the page, including a field the program never declared. A
// password manager's iframe, a `Prose` block holding markup somebody else
// wrote, a third-party embed — the program did not put those characters on
// the screen and has no business reading them.
//
// So the capability is narrowed here, where it is created, rather than
// labelled after the fact:
//
// 1. **The program named its key.** `onKey` compares and returns; a key
// the program did not name reaches nothing. The compiler emits the
// literal, so the set of observable keys is written in the source.
// 2. **No editable element had focus.** `isEditable` is what makes (1)
// safe for a printable key — `on key "r"` cannot see the `r` in a
// password, because while that password field has focus this listener
// stands down.
//
// Together: a handler learns only that the key it named itself was pressed
// while nobody was typing into anything. There is no payload, so there is
// nothing else to learn, and there is no argument about what to label.
//
// # The listener is removed
//
// `dom.js`'s `on` never detaches, and it is right not to: the node it is
// attached to is what gets removed. A document listener has no such node.
// One left behind is a leak *and* a correctness bug — it keeps firing into
// a graph whose signals nothing renders any more. `onCleanup` is registered
// against whatever `owned` scope is open, which is the branch closure
// `ifInto`, `whenInto` and `eachInto` already build and already dispose.
import from './signal.js';
/**
* Whether a keystroke aimed at `target` is somebody typing into something.
*
* Conservative by construction: the question asked is "could this element
* receive text", and anything unrecognised is treated as if it could not,
* only because an unrecognised element cannot receive text either — every
* text-receiving element in HTML is one of these three cases.
*
* `type` is not consulted. A `<input type="checkbox">` receives no
* characters, so suppressing there costs a shortcut and protects nothing —
* but reading `type` to allow it would be a list of the input types that
* are safe, and that list fails open the day a new one is added.
*/
/**
* Run `handler` when `key` is pressed and nobody is typing.
*
* `key` is compared against `KeyboardEvent.key` exactly, which is why the
* compiler checks the literal against a closed table: `"Esc"` is a listener
* that never fires, and a browser reports that as silence.
*
* Batched and contained for the reasons `dom.js`'s `on` is: several writes
* in one handler repaint once, and a throwing handler must not take the
* page's other listeners with it.
*/
export