@vui-rs/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/char-width.d.ts +7 -0
- package/dist/char-width.js +20 -0
- package/dist/color-names.js +46 -0
- package/dist/color.d.ts +5 -0
- package/dist/color.js +57 -0
- package/dist/image-decode.d.ts +21 -0
- package/dist/image-decode.js +42 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.js +27 -0
- package/dist/keys.d.ts +76 -0
- package/dist/keys.js +373 -0
- package/dist/named-colors.d.ts +7 -0
- package/dist/named-colors.js +20 -0
- package/dist/native/darwin-arm64/libvui_core.dylib +0 -0
- package/dist/native/darwin-x64/libvui_core.dylib +0 -0
- package/dist/native/ffi-symbols.d.ts +453 -0
- package/dist/native/ffi-symbols.js +680 -0
- package/dist/native/linux-arm64/libvui_core.so +0 -0
- package/dist/native/linux-x64/libvui_core.so +0 -0
- package/dist/native/load-native-lib.d.ts +384 -0
- package/dist/native/load-native-lib.js +63 -0
- package/dist/native/win32-x64/vui_core.dll +0 -0
- package/dist/node.d.ts +61 -0
- package/dist/node.js +157 -0
- package/dist/offscreen-buffer.d.ts +28 -0
- package/dist/offscreen-buffer.js +73 -0
- package/dist/renderer.d.ts +106 -0
- package/dist/renderer.js +186 -0
- package/dist/style.d.ts +48 -0
- package/dist/style.js +134 -0
- package/dist/terminal-session.d.ts +43 -0
- package/dist/terminal-session.js +82 -0
- package/dist/text/edit-buffer.d.ts +31 -0
- package/dist/text/edit-buffer.js +96 -0
- package/dist/text/editor-view.d.ts +22 -0
- package/dist/text/editor-view.js +48 -0
- package/dist/text/index.d.ts +5 -0
- package/dist/text/index.js +5 -0
- package/dist/text/text-buffer-view.d.ts +22 -0
- package/dist/text/text-buffer-view.js +49 -0
- package/dist/text/text-buffer.d.ts +16 -0
- package/dist/text/text-buffer.js +43 -0
- package/package.json +46 -0
package/dist/keys.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/keys.d.ts
|
|
2
|
+
interface KeyEvent {
|
|
3
|
+
type: 'key';
|
|
4
|
+
/** Lowercase name: a printable (`"a"`, `"世"`) or a named key (`"enter"`, `"up"`). */
|
|
5
|
+
name: string;
|
|
6
|
+
ctrl: boolean;
|
|
7
|
+
alt: boolean;
|
|
8
|
+
shift: boolean;
|
|
9
|
+
meta: boolean;
|
|
10
|
+
/** The exact source bytes (as a string) this event was parsed from. */
|
|
11
|
+
raw: string;
|
|
12
|
+
}
|
|
13
|
+
interface PasteEvent {
|
|
14
|
+
type: 'paste';
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
type MouseButton = 'left' | 'middle' | 'right' | 'wheelUp' | 'wheelDown';
|
|
18
|
+
interface MouseEvent {
|
|
19
|
+
type: 'mouse';
|
|
20
|
+
kind: 'down' | 'up' | 'move' | 'drag' | 'wheel';
|
|
21
|
+
button: MouseButton | null;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
ctrl: boolean;
|
|
25
|
+
alt: boolean;
|
|
26
|
+
shift: boolean;
|
|
27
|
+
meta: boolean;
|
|
28
|
+
raw: string;
|
|
29
|
+
}
|
|
30
|
+
type InputEvent = KeyEvent | PasteEvent | MouseEvent;
|
|
31
|
+
/** Parse a chunk of terminal input into discrete key/paste events (stateless). */
|
|
32
|
+
declare function parseKeys(data: string | Uint8Array): InputEvent[];
|
|
33
|
+
interface KeyDecoder {
|
|
34
|
+
/** Feed a chunk; returns the events decodable so far. Buffers a partial tail. */
|
|
35
|
+
feed(data: string | Uint8Array): InputEvent[];
|
|
36
|
+
/**
|
|
37
|
+
* Force-parse the buffered partial tail (best-effort: a lone ESC becomes a bare
|
|
38
|
+
* Escape) and clear it. Call on an idle/escape timeout so a standalone Escape
|
|
39
|
+
* keypress — indistinguishable from the start of a CSI/SS3 sequence until more
|
|
40
|
+
* bytes arrive — isn't held until the next key. No-op when nothing is pending.
|
|
41
|
+
*/
|
|
42
|
+
flush(): InputEvent[];
|
|
43
|
+
/** The currently-buffered partial tail (empty when fully drained). */
|
|
44
|
+
pending(): string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Stateful decoder for live input: it carries a partial trailing escape/paste
|
|
48
|
+
* across chunks, so a sequence (or a large paste) split over multiple stdin reads
|
|
49
|
+
* still parses correctly. One decoder per input stream.
|
|
50
|
+
*/
|
|
51
|
+
declare function createKeyDecoder(): KeyDecoder;
|
|
52
|
+
/**
|
|
53
|
+
* Test whether an event matches a key spec like `"ctrl+c"`, `"shift+tab"`,
|
|
54
|
+
* `"enter"`. Modifiers are order-insensitive; `super` is an alias for `meta`.
|
|
55
|
+
*/
|
|
56
|
+
declare function matchesKey(ev: InputEvent, spec: string): boolean;
|
|
57
|
+
/** Tiny helper for building key specs: `Key.ctrl("c")`, `Key.enter`. */
|
|
58
|
+
declare const Key: {
|
|
59
|
+
readonly ctrl: (k: string) => string;
|
|
60
|
+
readonly alt: (k: string) => string;
|
|
61
|
+
readonly shift: (k: string) => string;
|
|
62
|
+
readonly enter: "enter";
|
|
63
|
+
readonly tab: "tab";
|
|
64
|
+
readonly escape: "escape";
|
|
65
|
+
readonly backspace: "backspace";
|
|
66
|
+
readonly delete: "delete";
|
|
67
|
+
readonly up: "up";
|
|
68
|
+
readonly down: "down";
|
|
69
|
+
readonly left: "left";
|
|
70
|
+
readonly right: "right";
|
|
71
|
+
readonly home: "home";
|
|
72
|
+
readonly end: "end";
|
|
73
|
+
readonly space: "space";
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
export { InputEvent, Key, KeyDecoder, KeyEvent, MouseButton, MouseEvent, PasteEvent, createKeyDecoder, matchesKey, parseKeys };
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
//#region src/keys.ts
|
|
2
|
+
const decoder = new TextDecoder();
|
|
3
|
+
const PASTE_START = "\x1B[200~";
|
|
4
|
+
const PASTE_END = "\x1B[201~";
|
|
5
|
+
const MOUSE_BUTTONS = [
|
|
6
|
+
"left",
|
|
7
|
+
"middle",
|
|
8
|
+
"right"
|
|
9
|
+
];
|
|
10
|
+
const ARROW_NAMES = {
|
|
11
|
+
A: "up",
|
|
12
|
+
B: "down",
|
|
13
|
+
C: "right",
|
|
14
|
+
D: "left",
|
|
15
|
+
H: "home",
|
|
16
|
+
F: "end"
|
|
17
|
+
};
|
|
18
|
+
const TILDE_NAMES = {
|
|
19
|
+
"1": "home",
|
|
20
|
+
"2": "insert",
|
|
21
|
+
"3": "delete",
|
|
22
|
+
"4": "end",
|
|
23
|
+
"5": "pageUp",
|
|
24
|
+
"6": "pageDown",
|
|
25
|
+
"7": "home",
|
|
26
|
+
"8": "end"
|
|
27
|
+
};
|
|
28
|
+
const SS3_NAMES = {
|
|
29
|
+
...ARROW_NAMES,
|
|
30
|
+
P: "f1",
|
|
31
|
+
Q: "f2",
|
|
32
|
+
R: "f3",
|
|
33
|
+
S: "f4"
|
|
34
|
+
};
|
|
35
|
+
function key(name, opts = {}) {
|
|
36
|
+
return {
|
|
37
|
+
type: "key",
|
|
38
|
+
name,
|
|
39
|
+
ctrl: !!opts.ctrl,
|
|
40
|
+
alt: !!opts.alt,
|
|
41
|
+
shift: !!opts.shift,
|
|
42
|
+
meta: !!opts.meta,
|
|
43
|
+
raw: opts.raw ?? name
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Decode a CSI modifier param (`m` in `1;m`) into modifier flags. */
|
|
47
|
+
function decodeMod(param) {
|
|
48
|
+
const m = param ? Number.parseInt(param, 10) - 1 : 0;
|
|
49
|
+
return {
|
|
50
|
+
shift: !!(m & 1),
|
|
51
|
+
alt: !!(m & 2),
|
|
52
|
+
ctrl: !!(m & 4),
|
|
53
|
+
meta: !!(m & 8)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Kitty keyboard protocol functional keycodes that map to a named key. The
|
|
58
|
+
* protocol reports most printable keys by their Unicode codepoint (handled
|
|
59
|
+
* below) and reserves these specific codepoints for named keys. Arrows, Home/
|
|
60
|
+
* End, etc. keep their legacy `CSI letter`/`CSI ~` encodings even in Kitty mode,
|
|
61
|
+
* so they continue through the existing decoder and aren't listed here.
|
|
62
|
+
*/
|
|
63
|
+
const KITTY_NAMED = {
|
|
64
|
+
13: "enter",
|
|
65
|
+
27: "escape",
|
|
66
|
+
9: "tab",
|
|
67
|
+
127: "backspace",
|
|
68
|
+
32: "space"
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Decode a Kitty modifier+event field (`mods` or `mods:event-type`). The modifier
|
|
72
|
+
* bitfield is value-minus-1; bit layout: shift1 alt2 ctrl4 super8 hyper16 meta32.
|
|
73
|
+
* `super`/`meta` both fold to our `meta` flag. Event type: 1 press, 2 repeat, 3
|
|
74
|
+
* release (default 1 when absent).
|
|
75
|
+
*/
|
|
76
|
+
function decodeKittyMod(param) {
|
|
77
|
+
const [modStr, evStr] = (param ?? "").split(":");
|
|
78
|
+
const m = modStr ? Number.parseInt(modStr, 10) - 1 : 0;
|
|
79
|
+
const eventType = evStr ? Number.parseInt(evStr, 10) : 1;
|
|
80
|
+
return {
|
|
81
|
+
mods: {
|
|
82
|
+
shift: !!(m & 1),
|
|
83
|
+
alt: !!(m & 2),
|
|
84
|
+
ctrl: !!(m & 4),
|
|
85
|
+
meta: !!(m & 8) || !!(m & 32)
|
|
86
|
+
},
|
|
87
|
+
eventType
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Decode a Kitty keyboard `CSI <code>[:alt:base] ; <mods>[:event] u` sequence into
|
|
92
|
+
* a KeyEvent. Key releases (event type 3) are dropped in v0; repeats (2) fire as a
|
|
93
|
+
* normal press. Malformed/unmapped control keycodes are consumed silently.
|
|
94
|
+
*/
|
|
95
|
+
function parseCsiU(params, raw, consumed, out) {
|
|
96
|
+
const parts = params.split(";");
|
|
97
|
+
const keycode = Number.parseInt(parts[0].split(":")[0] ?? "", 10);
|
|
98
|
+
if (!Number.isFinite(keycode)) return consumed;
|
|
99
|
+
const { mods, eventType } = decodeKittyMod(parts[1]);
|
|
100
|
+
if (eventType === 3) return consumed;
|
|
101
|
+
let name = KITTY_NAMED[keycode];
|
|
102
|
+
if (name === void 0) if (keycode >= 32 && keycode !== 127) name = String.fromCodePoint(keycode);
|
|
103
|
+
else return consumed;
|
|
104
|
+
out.push(key(name, {
|
|
105
|
+
...mods,
|
|
106
|
+
raw
|
|
107
|
+
}));
|
|
108
|
+
return consumed;
|
|
109
|
+
}
|
|
110
|
+
function newMouseState() {
|
|
111
|
+
return { buttons: /* @__PURE__ */ new Set() };
|
|
112
|
+
}
|
|
113
|
+
function stepOne(s, i, out, mouse) {
|
|
114
|
+
const code = s.charCodeAt(i);
|
|
115
|
+
if (code === 27) {
|
|
116
|
+
const r = parseEscape(s, i, out, mouse);
|
|
117
|
+
if (r !== 0) return r;
|
|
118
|
+
out.push(key("escape", { raw: "\x1B" }));
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (code === 13 || code === 10) out.push(key("enter", { raw: s[i] }));
|
|
122
|
+
else if (code === 9) out.push(key("tab", { raw: s[i] }));
|
|
123
|
+
else if (code === 127 || code === 8) out.push(key("backspace", { raw: s[i] }));
|
|
124
|
+
else if (code === 0) out.push(key("space", {
|
|
125
|
+
ctrl: true,
|
|
126
|
+
raw: s[i]
|
|
127
|
+
}));
|
|
128
|
+
else if (code >= 1 && code <= 26) out.push(key(String.fromCharCode(code + 96), {
|
|
129
|
+
ctrl: true,
|
|
130
|
+
raw: s[i]
|
|
131
|
+
}));
|
|
132
|
+
else if (code >= 28 && code <= 31) {} else {
|
|
133
|
+
const ch = String.fromCodePoint(s.codePointAt(i));
|
|
134
|
+
out.push(key(ch, {
|
|
135
|
+
raw: ch,
|
|
136
|
+
shift: ch.length === 1 && ch >= "A" && ch <= "Z"
|
|
137
|
+
}));
|
|
138
|
+
return ch.length;
|
|
139
|
+
}
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
/** Parse a chunk of terminal input into discrete key/paste events (stateless). */
|
|
143
|
+
function parseKeys(data) {
|
|
144
|
+
const s = typeof data === "string" ? data : decoder.decode(data);
|
|
145
|
+
const events = [];
|
|
146
|
+
const mouse = newMouseState();
|
|
147
|
+
let i = 0;
|
|
148
|
+
while (i < s.length) {
|
|
149
|
+
const consumed = stepOne(s, i, events, mouse);
|
|
150
|
+
if (consumed === -1) {
|
|
151
|
+
events.push(key("escape", { raw: "\x1B" }));
|
|
152
|
+
i += 1;
|
|
153
|
+
} else i += consumed;
|
|
154
|
+
}
|
|
155
|
+
return events;
|
|
156
|
+
}
|
|
157
|
+
/** A bare partial CSI/SS3 (no terminator) buffered this long is treated as stuck
|
|
158
|
+
* and flushed, so a malformed stream can't grow the pending buffer unbounded. A
|
|
159
|
+
* bracketed paste (identified by its start marker) is exempt — pastes are large
|
|
160
|
+
* by nature and must buffer until their end marker. */
|
|
161
|
+
const MAX_PENDING = 64;
|
|
162
|
+
/**
|
|
163
|
+
* Stateful decoder for live input: it carries a partial trailing escape/paste
|
|
164
|
+
* across chunks, so a sequence (or a large paste) split over multiple stdin reads
|
|
165
|
+
* still parses correctly. One decoder per input stream.
|
|
166
|
+
*/
|
|
167
|
+
function createKeyDecoder() {
|
|
168
|
+
let pending = "";
|
|
169
|
+
const mouse = newMouseState();
|
|
170
|
+
return {
|
|
171
|
+
feed(data) {
|
|
172
|
+
const s = pending + (typeof data === "string" ? data : decoder.decode(data));
|
|
173
|
+
const events = [];
|
|
174
|
+
let i = 0;
|
|
175
|
+
while (i < s.length) {
|
|
176
|
+
const consumed = stepOne(s, i, events, mouse);
|
|
177
|
+
if (consumed === -1) break;
|
|
178
|
+
i += consumed;
|
|
179
|
+
}
|
|
180
|
+
pending = s.slice(i);
|
|
181
|
+
if (pending.length > MAX_PENDING && !pending.startsWith(PASTE_START)) {
|
|
182
|
+
for (const ev of parseKeys(pending)) events.push(ev);
|
|
183
|
+
pending = "";
|
|
184
|
+
}
|
|
185
|
+
return events;
|
|
186
|
+
},
|
|
187
|
+
flush() {
|
|
188
|
+
if (pending === "" || pending.startsWith(PASTE_START)) return [];
|
|
189
|
+
const events = parseKeys(pending);
|
|
190
|
+
pending = "";
|
|
191
|
+
return events;
|
|
192
|
+
},
|
|
193
|
+
pending() {
|
|
194
|
+
return pending;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parse an escape sequence starting at `i`. Returns bytes consumed, `0` for a
|
|
200
|
+
* bare/unrecognised ESC (caller emits Escape), or `-1` for a truncated sequence
|
|
201
|
+
* that needs more input.
|
|
202
|
+
*/
|
|
203
|
+
function parseEscape(s, i, out, mouse) {
|
|
204
|
+
const next = s[i + 1];
|
|
205
|
+
if (next === void 0) return -1;
|
|
206
|
+
if (next === "[") return parseCSI(s, i, out, mouse);
|
|
207
|
+
if (next === "O") return parseSS3(s, i, out);
|
|
208
|
+
const code = s.charCodeAt(i + 1);
|
|
209
|
+
if (code === 127 || code === 8) {
|
|
210
|
+
out.push(key("backspace", {
|
|
211
|
+
alt: true,
|
|
212
|
+
raw: s.slice(i, i + 2)
|
|
213
|
+
}));
|
|
214
|
+
return 2;
|
|
215
|
+
}
|
|
216
|
+
if (code >= 32) {
|
|
217
|
+
const ch = String.fromCodePoint(s.codePointAt(i + 1));
|
|
218
|
+
out.push(key(ch, {
|
|
219
|
+
alt: true,
|
|
220
|
+
raw: "\x1B" + ch
|
|
221
|
+
}));
|
|
222
|
+
return 1 + ch.length;
|
|
223
|
+
}
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
function parseCSI(s, i, out, mouse) {
|
|
227
|
+
if (s[i + 2] === "<") return parseSgrMouse(s, i, out, mouse);
|
|
228
|
+
if (s[i + 2] === "M") return parseX10Mouse(s, i, out, mouse);
|
|
229
|
+
let j = i + 2;
|
|
230
|
+
let params = "";
|
|
231
|
+
while (j < s.length && (s[j] === ";" || s[j] === ":" || s[j] >= "0" && s[j] <= "9")) {
|
|
232
|
+
params += s[j];
|
|
233
|
+
j += 1;
|
|
234
|
+
}
|
|
235
|
+
const final = s[j];
|
|
236
|
+
if (final === void 0) return -1;
|
|
237
|
+
if (params === "200" && final === "~") {
|
|
238
|
+
const start = j + 1;
|
|
239
|
+
const end = s.indexOf(PASTE_END, start);
|
|
240
|
+
if (end === -1) return -1;
|
|
241
|
+
out.push({
|
|
242
|
+
type: "paste",
|
|
243
|
+
text: s.slice(start, end)
|
|
244
|
+
});
|
|
245
|
+
return end + 6 - i;
|
|
246
|
+
}
|
|
247
|
+
const raw = s.slice(i, j + 1);
|
|
248
|
+
const consumed = j + 1 - i;
|
|
249
|
+
if (final === "u") return parseCsiU(params, raw, consumed, out);
|
|
250
|
+
if (final === "Z") {
|
|
251
|
+
out.push(key("tab", {
|
|
252
|
+
shift: true,
|
|
253
|
+
raw
|
|
254
|
+
}));
|
|
255
|
+
return consumed;
|
|
256
|
+
}
|
|
257
|
+
const parts = params.split(";");
|
|
258
|
+
const mods = {
|
|
259
|
+
...decodeMod(parts[1]),
|
|
260
|
+
raw
|
|
261
|
+
};
|
|
262
|
+
if (ARROW_NAMES[final]) {
|
|
263
|
+
out.push(key(ARROW_NAMES[final], mods));
|
|
264
|
+
return consumed;
|
|
265
|
+
}
|
|
266
|
+
if (final === "~" && TILDE_NAMES[parts[0]]) {
|
|
267
|
+
out.push(key(TILDE_NAMES[parts[0]], mods));
|
|
268
|
+
return consumed;
|
|
269
|
+
}
|
|
270
|
+
return consumed;
|
|
271
|
+
}
|
|
272
|
+
function decodeMouseModifiers(code) {
|
|
273
|
+
return {
|
|
274
|
+
shift: !!(code & 4),
|
|
275
|
+
alt: !!(code & 8),
|
|
276
|
+
ctrl: !!(code & 16),
|
|
277
|
+
meta: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function mouseButton(code) {
|
|
281
|
+
if (code & 64) return code & 1 ? "wheelDown" : "wheelUp";
|
|
282
|
+
return MOUSE_BUTTONS[code & 3] ?? null;
|
|
283
|
+
}
|
|
284
|
+
function pushMouse(out, mouse, code, x, y, final, raw) {
|
|
285
|
+
const wheel = !!(code & 64);
|
|
286
|
+
const motion = !!(code & 32);
|
|
287
|
+
const button = mouseButton(code);
|
|
288
|
+
const mods = decodeMouseModifiers(code);
|
|
289
|
+
let kind;
|
|
290
|
+
if (wheel) kind = "wheel";
|
|
291
|
+
else if (final === "m") kind = "up";
|
|
292
|
+
else if (motion) kind = mouse.buttons.size > 0 ? "drag" : "move";
|
|
293
|
+
else kind = "down";
|
|
294
|
+
if (kind === "down" && button) mouse.buttons.add(button);
|
|
295
|
+
if (kind === "up") if (button) mouse.buttons.delete(button);
|
|
296
|
+
else mouse.buttons.clear();
|
|
297
|
+
out.push({
|
|
298
|
+
type: "mouse",
|
|
299
|
+
kind,
|
|
300
|
+
button,
|
|
301
|
+
x,
|
|
302
|
+
y,
|
|
303
|
+
ctrl: !!mods.ctrl,
|
|
304
|
+
alt: !!mods.alt,
|
|
305
|
+
shift: !!mods.shift,
|
|
306
|
+
meta: false,
|
|
307
|
+
raw
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function parseSgrMouse(s, i, out, mouse) {
|
|
311
|
+
let j = i + 3;
|
|
312
|
+
while (j < s.length && (s[j] === ";" || s[j] >= "0" && s[j] <= "9")) j += 1;
|
|
313
|
+
const final = s[j];
|
|
314
|
+
if (final === void 0) return -1;
|
|
315
|
+
if (final !== "M" && final !== "m") return j + 1 - i;
|
|
316
|
+
const raw = s.slice(i, j + 1);
|
|
317
|
+
const parts = s.slice(i + 3, j).split(";");
|
|
318
|
+
if (parts.length !== 3) return j + 1 - i;
|
|
319
|
+
const code = Number.parseInt(parts[0], 10);
|
|
320
|
+
const x = Number.parseInt(parts[1], 10) - 1;
|
|
321
|
+
const y = Number.parseInt(parts[2], 10) - 1;
|
|
322
|
+
if (!Number.isFinite(code) || !Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) return j + 1 - i;
|
|
323
|
+
pushMouse(out, mouse, code, x, y, final, raw);
|
|
324
|
+
return j + 1 - i;
|
|
325
|
+
}
|
|
326
|
+
function parseX10Mouse(s, i, out, mouse) {
|
|
327
|
+
if (i + 5 >= s.length) return -1;
|
|
328
|
+
const raw = s.slice(i, i + 6);
|
|
329
|
+
const code = s.charCodeAt(i + 3) - 32;
|
|
330
|
+
const x = s.charCodeAt(i + 4) - 33;
|
|
331
|
+
const y = s.charCodeAt(i + 5) - 33;
|
|
332
|
+
if (x < 0 || y < 0) return 6;
|
|
333
|
+
pushMouse(out, mouse, code, x, y, code === 3 ? "m" : "M", raw);
|
|
334
|
+
return 6;
|
|
335
|
+
}
|
|
336
|
+
function parseSS3(s, i, out) {
|
|
337
|
+
const final = s[i + 2];
|
|
338
|
+
if (final === void 0) return -1;
|
|
339
|
+
const name = SS3_NAMES[final];
|
|
340
|
+
if (!name) return 0;
|
|
341
|
+
out.push(key(name, { raw: s.slice(i, i + 3) }));
|
|
342
|
+
return 3;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Test whether an event matches a key spec like `"ctrl+c"`, `"shift+tab"`,
|
|
346
|
+
* `"enter"`. Modifiers are order-insensitive; `super` is an alias for `meta`.
|
|
347
|
+
*/
|
|
348
|
+
function matchesKey(ev, spec) {
|
|
349
|
+
if (ev.type !== "key") return false;
|
|
350
|
+
const parts = spec.toLowerCase().split("+");
|
|
351
|
+
const base = parts.pop();
|
|
352
|
+
return ev.name.toLowerCase() === base && ev.ctrl === parts.includes("ctrl") && ev.alt === parts.includes("alt") && ev.shift === parts.includes("shift") && ev.meta === (parts.includes("meta") || parts.includes("super"));
|
|
353
|
+
}
|
|
354
|
+
/** Tiny helper for building key specs: `Key.ctrl("c")`, `Key.enter`. */
|
|
355
|
+
const Key = {
|
|
356
|
+
ctrl: (k) => `ctrl+${k}`,
|
|
357
|
+
alt: (k) => `alt+${k}`,
|
|
358
|
+
shift: (k) => `shift+${k}`,
|
|
359
|
+
enter: "enter",
|
|
360
|
+
tab: "tab",
|
|
361
|
+
escape: "escape",
|
|
362
|
+
backspace: "backspace",
|
|
363
|
+
delete: "delete",
|
|
364
|
+
up: "up",
|
|
365
|
+
down: "down",
|
|
366
|
+
left: "left",
|
|
367
|
+
right: "right",
|
|
368
|
+
home: "home",
|
|
369
|
+
end: "end",
|
|
370
|
+
space: "space"
|
|
371
|
+
};
|
|
372
|
+
//#endregion
|
|
373
|
+
export { Key, createKeyDecoder, matchesKey, parseKeys };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/named-colors.d.ts
|
|
2
|
+
/** Parse a `#rgb`/`#rrggbb`/`#rrggbbaa` string to packed `0xRRGGBBAA`, or undefined. */
|
|
3
|
+
declare function parseHex(value: string): number | undefined;
|
|
4
|
+
/** Named color → packed `0xRRGGBBAA`. Built once from the shared JSON table. */
|
|
5
|
+
declare const NAMED_COLORS: ReadonlyMap<string, number>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { NAMED_COLORS, parseHex };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import color_names_default from "./color-names.js";
|
|
2
|
+
//#region src/named-colors.ts
|
|
3
|
+
/** Parse a `#rgb`/`#rrggbb`/`#rrggbbaa` string to packed `0xRRGGBBAA`, or undefined. */
|
|
4
|
+
function parseHex(value) {
|
|
5
|
+
if (!value.startsWith("#")) return void 0;
|
|
6
|
+
let hex = value.slice(1);
|
|
7
|
+
if (hex.length === 3) hex = hex.split("").map((c) => c + c).join("");
|
|
8
|
+
if (hex.length === 6) hex += "ff";
|
|
9
|
+
if (hex.length !== 8) return void 0;
|
|
10
|
+
if (!/^[0-9a-fA-F]{8}$/.test(hex)) return void 0;
|
|
11
|
+
return Number.parseInt(hex, 16) >>> 0;
|
|
12
|
+
}
|
|
13
|
+
/** Named color → packed `0xRRGGBBAA`. Built once from the shared JSON table. */
|
|
14
|
+
const NAMED_COLORS = new Map(Object.entries(color_names_default).map(([name, hex]) => {
|
|
15
|
+
const packed = parseHex(hex);
|
|
16
|
+
if (packed === void 0) throw new Error(`color-names.json: bad hex "${hex}" for "${name}"`);
|
|
17
|
+
return [name, packed];
|
|
18
|
+
}));
|
|
19
|
+
//#endregion
|
|
20
|
+
export { NAMED_COLORS, parseHex };
|
|
Binary file
|
|
Binary file
|