@neuroplastio/xterm-addon-hotty 0.1.0-next.2 → 0.1.0-next.4
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 +10 -0
- package/dist/hotty-xterm.js +107 -44
- package/dist/hotty-xterm.js.map +3 -3
- package/dist/types/addon.d.ts +10 -0
- package/dist/types/steps.d.ts +9 -0
- package/dist/types/surface.d.ts +21 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -183,6 +183,16 @@ runs programs for whoever connects.
|
|
|
183
183
|
click where it began. A second finger ends it with `dragend` and no
|
|
184
184
|
target, and nothing more is heard until every finger lifts. A long press,
|
|
185
185
|
or Alt held at the touch, never drags. Any other touch pans as below.
|
|
186
|
+
|
|
187
|
+
An element that also has `data-steps="<x> [<y>]"` (whole numbers, 0 for
|
|
188
|
+
none along that axis) says where in it the pointer is: every event of a
|
|
189
|
+
drag of it carries `x` and `y`, the pointer's step along its border box
|
|
190
|
+
(`getBoundingClientRect`, as it is at each move), rounded to the nearest
|
|
191
|
+
and clamped to 0 and the count, wherever the pointer or the finger is.
|
|
192
|
+
A `drag` also goes out each time the step changes. The counts are read
|
|
193
|
+
when the drag starts; once the element has left the document, the step
|
|
194
|
+
stays as it last was, as it does in a `dragend` that ends a drag early.
|
|
195
|
+
The capabilities report `steps: true`.
|
|
186
196
|
- **Presses** (SPEC §5.2, §9: `p=1` on `a=place`). Every primary press of a
|
|
187
197
|
mouse or a pen in the window reports `press` on its pointerdown, before
|
|
188
198
|
the drag and the focus it causes; a tap reports it on the mousedown the
|
package/dist/hotty-xterm.js
CHANGED
|
@@ -683,6 +683,21 @@ var Touch = class {
|
|
|
683
683
|
}
|
|
684
684
|
};
|
|
685
685
|
|
|
686
|
+
// src/steps.ts
|
|
687
|
+
function parseSteps(value) {
|
|
688
|
+
if (value === null) return null;
|
|
689
|
+
const words = value.trim().split(/\s+/);
|
|
690
|
+
if (words.length > 2 || !words.every((w) => /^[0-9]+$/.test(w))) return null;
|
|
691
|
+
const [x, y = 0] = words.map(Number);
|
|
692
|
+
if (!Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x === 0 && y === 0) return null;
|
|
693
|
+
return [x, y];
|
|
694
|
+
}
|
|
695
|
+
function stepAt(offset, size, count) {
|
|
696
|
+
if (!(size > 0)) return 0;
|
|
697
|
+
const at = Math.min(Math.max(offset / size, 0), 1) * count;
|
|
698
|
+
return Math.min(Math.floor(at + 0.5), count);
|
|
699
|
+
}
|
|
700
|
+
|
|
686
701
|
// src/resolver.ts
|
|
687
702
|
var DROP = /* @__PURE__ */ new Set(["script", "iframe", "frame", "frameset", "object", "embed", "applet", "base", "meta", "portal", "fencedframe"]);
|
|
688
703
|
var DROP_ATTRS = /* @__PURE__ */ new Set(["srcdoc", "autofocus", "http-equiv", "ping", "nonce", "integrity"]);
|
|
@@ -2289,25 +2304,49 @@ var Surface = class {
|
|
|
2289
2304
|
if (!id) return;
|
|
2290
2305
|
const [c, r] = this.cellOf(e);
|
|
2291
2306
|
const keys = keysOf(e);
|
|
2292
|
-
|
|
2293
|
-
this.
|
|
2307
|
+
const d = { pointer: e.pointerId, start: id, steps: stepsOf(start), target: id, c, r, keys, step: {} };
|
|
2308
|
+
d.step = this.stepIn(d, e.clientX, e.clientY);
|
|
2309
|
+
this.drag = d;
|
|
2310
|
+
this.emit("dragstart", id, { c, r, keys, ...d.step });
|
|
2294
2311
|
try {
|
|
2295
2312
|
this.doc.documentElement.setPointerCapture(e.pointerId);
|
|
2296
2313
|
} catch {
|
|
2297
2314
|
}
|
|
2298
2315
|
}
|
|
2299
|
-
/** A move during a drag
|
|
2300
|
-
* while it has none, each time its cell does. */
|
|
2316
|
+
/** A move during a drag (`moveDrag`). */
|
|
2301
2317
|
onDragMove(e) {
|
|
2302
2318
|
const d = this.drag;
|
|
2303
2319
|
if (!d || e.pointerId !== d.pointer) return;
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2320
|
+
this.moveDrag(d, e.clientX, e.clientY, keysOf(e));
|
|
2321
|
+
}
|
|
2322
|
+
/** The pointer or the finger of a drag moved to (x, y) of the document:
|
|
2323
|
+
* an event each time its target changes, while it has none each time its
|
|
2324
|
+
* cell does, and each time its step does (SPEC §9.1); one for a move that
|
|
2325
|
+
* changes more than one. */
|
|
2326
|
+
moveDrag(d, x, y, keys) {
|
|
2327
|
+
const [c, r] = this.cellAt(x, y);
|
|
2328
|
+
const target = this.dragTarget(this.elementAtPoint(x, y, c, r));
|
|
2329
|
+
const step = this.stepIn(d, x, y);
|
|
2330
|
+
if (target !== d.target || target === "" && (c !== d.c || r !== d.r) || step.x !== d.step.x || step.y !== d.step.y) {
|
|
2331
|
+
this.emit("drag", target, { c, r, keys, ...step });
|
|
2309
2332
|
}
|
|
2310
|
-
Object.assign(d, { target, c, r, keys });
|
|
2333
|
+
Object.assign(d, { target, c, r, keys, step });
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Where (x, y) of the document is in the element a drag started on, in
|
|
2337
|
+
* its steps (SPEC §9.1): measured against its border box where the user
|
|
2338
|
+
* sees it now, wherever the point is, and as they last were once the
|
|
2339
|
+
* element has left the document. None when it has no steps.
|
|
2340
|
+
*/
|
|
2341
|
+
stepIn(d, x, y) {
|
|
2342
|
+
const s = d.steps;
|
|
2343
|
+
if (!s) return {};
|
|
2344
|
+
if (!s.el.isConnected) return d.step;
|
|
2345
|
+
const box = s.el.getBoundingClientRect();
|
|
2346
|
+
const step = {};
|
|
2347
|
+
if (s.x) step.x = stepAt(x - box.left, box.width, s.x);
|
|
2348
|
+
if (s.y) step.y = stepAt(y - box.top, box.height, s.y);
|
|
2349
|
+
return step;
|
|
2311
2350
|
}
|
|
2312
2351
|
/** The release ends the drag, wherever it is. Where it began, it is the
|
|
2313
2352
|
* click it would have been without the drag; anywhere else, no click. */
|
|
@@ -2318,7 +2357,7 @@ var Surface = class {
|
|
|
2318
2357
|
const [c, r] = this.cellOf(e);
|
|
2319
2358
|
const at = this.elementAt(e, c, r);
|
|
2320
2359
|
const target = this.dragTarget(at);
|
|
2321
|
-
this.emit("dragend", target, { c, r, keys: keysOf(e) });
|
|
2360
|
+
this.emit("dragend", target, { c, r, keys: keysOf(e), ...this.stepIn(d, e.clientX, e.clientY) });
|
|
2322
2361
|
this.dragReleased = true;
|
|
2323
2362
|
setTimeout(() => this.dragReleased = false, 0);
|
|
2324
2363
|
if (target === d.start && at) this.reportClick(ancestry(at));
|
|
@@ -2331,13 +2370,13 @@ var Surface = class {
|
|
|
2331
2370
|
/**
|
|
2332
2371
|
* Ends a drag under way without a release (SPEC §9.1: its placement went
|
|
2333
2372
|
* away, a new document came, or the pointer was lost): `dragend` with no
|
|
2334
|
-
* target, at the last cell the program heard of.
|
|
2373
|
+
* target, at the last cell and step the program heard of.
|
|
2335
2374
|
*/
|
|
2336
2375
|
cancelDrag() {
|
|
2337
2376
|
const d = this.drag;
|
|
2338
2377
|
if (!d) return;
|
|
2339
2378
|
this.dropDrag();
|
|
2340
|
-
this.emit("dragend", "", { c: d.c, r: d.r, keys: d.keys });
|
|
2379
|
+
this.emit("dragend", "", { c: d.c, r: d.r, keys: d.keys, ...d.step });
|
|
2341
2380
|
}
|
|
2342
2381
|
/** Ends a drag under way, silently (a detached or deleted surface). */
|
|
2343
2382
|
dropDrag() {
|
|
@@ -2454,25 +2493,20 @@ var Surface = class {
|
|
|
2454
2493
|
const [c, r] = this.cellAt(begin.x, begin.y);
|
|
2455
2494
|
const keys = keysOf(begin);
|
|
2456
2495
|
this.reportPress(e);
|
|
2457
|
-
|
|
2458
|
-
this.
|
|
2496
|
+
const d = { pointer: NaN, start: id, steps: stepsOf(start), target: id, c, r, keys, step: {} };
|
|
2497
|
+
d.step = this.stepIn(d, begin.x, begin.y);
|
|
2498
|
+
this.drag = d;
|
|
2499
|
+
this.emit("dragstart", id, { c, r, keys, ...d.step });
|
|
2459
2500
|
this.pressAt(e);
|
|
2460
2501
|
const t = [...e.changedTouches][0];
|
|
2461
2502
|
if (t) this.touchDragMove(t.clientX, t.clientY, e);
|
|
2462
2503
|
return true;
|
|
2463
2504
|
}
|
|
2464
2505
|
/** A touch drag's finger moved to (x, y) of the document: as a pointer's
|
|
2465
|
-
* move (`
|
|
2506
|
+
* move (`moveDrag`). */
|
|
2466
2507
|
touchDragMove(x, y, e) {
|
|
2467
2508
|
const d = this.drag;
|
|
2468
|
-
if (
|
|
2469
|
-
const [c, r] = this.cellAt(x, y);
|
|
2470
|
-
const target = this.dragTarget(this.elementAtPoint(x, y, c, r));
|
|
2471
|
-
const keys = keysOf(e);
|
|
2472
|
-
if (target !== d.target || target === "" && (c !== d.c || r !== d.r)) {
|
|
2473
|
-
this.emit("drag", target, { c, r, keys });
|
|
2474
|
-
}
|
|
2475
|
-
Object.assign(d, { target, c, r, keys });
|
|
2509
|
+
if (d) this.moveDrag(d, x, y, keysOf(e));
|
|
2476
2510
|
}
|
|
2477
2511
|
/** A touch drag's finger lifted at (x, y): as a pointer's release
|
|
2478
2512
|
* (`onDragUp`), a click where it began. */
|
|
@@ -2483,7 +2517,7 @@ var Surface = class {
|
|
|
2483
2517
|
const [c, r] = this.cellAt(x, y);
|
|
2484
2518
|
const at = this.elementAtPoint(x, y, c, r);
|
|
2485
2519
|
const target = this.dragTarget(at);
|
|
2486
|
-
this.emit("dragend", target, { c, r, keys: keysOf(e) });
|
|
2520
|
+
this.emit("dragend", target, { c, r, keys: keysOf(e), ...this.stepIn(d, x, y) });
|
|
2487
2521
|
this.dragReleased = true;
|
|
2488
2522
|
setTimeout(() => this.dragReleased = false, 0);
|
|
2489
2523
|
if (target === d.start && at) this.reportClick(ancestry(at));
|
|
@@ -2573,17 +2607,21 @@ var Surface = class {
|
|
|
2573
2607
|
/**
|
|
2574
2608
|
* The cells an element's border box covers as the user sees it, scrolled
|
|
2575
2609
|
* included (SPEC §9: `area`), counted from the surface's top left cell:
|
|
2576
|
-
* whole, where it is clipped or scrolled away. An edge
|
|
2577
|
-
*
|
|
2578
|
-
*
|
|
2610
|
+
* whole, where it is clipped or scrolled away. An edge close enough to a
|
|
2611
|
+
* cell's is on it: within half a device pixel, which the user cannot see,
|
|
2612
|
+
* plus what layout loses on the way there. Layout keeps lengths in fixed
|
|
2613
|
+
* units (1/64 px in Chromium, 1/60 in Firefox) and cuts each box's to
|
|
2614
|
+
* them, so with a cell a fraction of a pixel, a row 29 boxes of one cell
|
|
2615
|
+
* down starts up to 29/64 px short of its cell.
|
|
2579
2616
|
*/
|
|
2580
2617
|
areaOf(el) {
|
|
2581
2618
|
const b = el.getBoundingClientRect();
|
|
2582
|
-
const
|
|
2583
|
-
const
|
|
2584
|
-
const
|
|
2585
|
-
const
|
|
2586
|
-
const
|
|
2619
|
+
const half = 0.5 / (this.frame.ownerDocument.defaultView?.devicePixelRatio || 1);
|
|
2620
|
+
const slack = (edge, cell) => half + Math.abs(edge / cell) / 60;
|
|
2621
|
+
const c = Math.floor((b.left + slack(b.left, this.cellW)) / this.cellW);
|
|
2622
|
+
const r = Math.floor((b.top + slack(b.top, this.cellH)) / this.cellH);
|
|
2623
|
+
const right = Math.ceil((b.right - slack(b.right, this.cellW)) / this.cellW);
|
|
2624
|
+
const bottom = Math.ceil((b.bottom - slack(b.bottom, this.cellH)) / this.cellH);
|
|
2587
2625
|
return { c, r, w: Math.max(0, right - c), h: Math.max(0, bottom - r) };
|
|
2588
2626
|
}
|
|
2589
2627
|
// --- Events for the program (PROTOCOL §8) --------------------------------
|
|
@@ -2973,6 +3011,10 @@ function isElement(n) {
|
|
|
2973
3011
|
function listens(el, what) {
|
|
2974
3012
|
return (el.getAttribute("data-on") ?? "").split(/\s+/).includes(what);
|
|
2975
3013
|
}
|
|
3014
|
+
function stepsOf(el) {
|
|
3015
|
+
const n = parseSteps(el.getAttribute("data-steps"));
|
|
3016
|
+
return n && { el, x: n[0], y: n[1] };
|
|
3017
|
+
}
|
|
2976
3018
|
function ancestry(el) {
|
|
2977
3019
|
const out = [];
|
|
2978
3020
|
for (let n = el; n; n = n.parentElement) out.push(n);
|
|
@@ -3280,6 +3322,9 @@ var HottyAddon = class {
|
|
|
3280
3322
|
css = "";
|
|
3281
3323
|
policy;
|
|
3282
3324
|
metrics = { cellW: 0, cellH: 0, key: "" };
|
|
3325
|
+
/** The cell (CSS px) and font the program last heard of, or saw at the
|
|
3326
|
+
* start: a `resize` is owed when both have changed since (SPEC §5.3). */
|
|
3327
|
+
told = { cellW: 0, cellH: 0, font: "" };
|
|
3283
3328
|
resizeTimer = null;
|
|
3284
3329
|
/** A surface's key is being forwarded to the terminal (`key`), and the
|
|
3285
3330
|
* terminal's own focus is held off meanwhile. A program in the page may
|
|
@@ -3516,6 +3561,7 @@ var HottyAddon = class {
|
|
|
3516
3561
|
limits: { resources: this.store.quota, surfaces: this.opts.maxSurfaces },
|
|
3517
3562
|
net: this.policy,
|
|
3518
3563
|
scroll: true,
|
|
3564
|
+
steps: true,
|
|
3519
3565
|
host: HOST,
|
|
3520
3566
|
version: VERSION
|
|
3521
3567
|
};
|
|
@@ -3933,23 +3979,40 @@ var HottyAddon = class {
|
|
|
3933
3979
|
const o = this.term.options;
|
|
3934
3980
|
const key = `${cellW}x${cellH}|${o.fontFamily}|${o.fontSize}|${JSON.stringify(o.theme ?? {})}`;
|
|
3935
3981
|
if (key === this.metrics.key) return;
|
|
3936
|
-
const
|
|
3982
|
+
const first = this.metrics.cellW === 0;
|
|
3937
3983
|
this.metrics = { cellW, cellH, key };
|
|
3984
|
+
if (first) this.told = { cellW, cellH, font: this.font() };
|
|
3985
|
+
else this.settle();
|
|
3938
3986
|
if (this.surfaces.size === 0) return;
|
|
3939
3987
|
const css = this.hostCss();
|
|
3940
3988
|
const scheme = this.scheme();
|
|
3941
3989
|
for (const s of this.surfaces.values()) s.setHostCss(css, scheme);
|
|
3942
3990
|
this.reposition();
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3991
|
+
}
|
|
3992
|
+
font() {
|
|
3993
|
+
return `${this.term.options.fontFamily}|${this.term.options.fontSize}`;
|
|
3994
|
+
}
|
|
3995
|
+
/** Once the cell and font settle: a new font that changed the cell is
|
|
3996
|
+
* re-rendered with no program involvement, so the program hears its new
|
|
3997
|
+
* size (SPEC §5.3, §9). A browser's zoom moves the cell only by device
|
|
3998
|
+
* pixel rounding and keeps the font, and is not told. xterm.js may take
|
|
3999
|
+
* the font and its cell in two renders, so this waits for both. */
|
|
4000
|
+
settle() {
|
|
4001
|
+
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
4002
|
+
this.resizeTimer = setTimeout(() => {
|
|
4003
|
+
this.resizeTimer = null;
|
|
4004
|
+
const { cellW, cellH } = this.cell();
|
|
4005
|
+
const font = this.font();
|
|
4006
|
+
const t = this.told;
|
|
4007
|
+
const owed = font !== t.font && (cellW !== t.cellW || cellH !== t.cellH);
|
|
4008
|
+
this.told = { cellW, cellH, font };
|
|
4009
|
+
if (!owed) return;
|
|
4010
|
+
for (const p of this.placements.values()) {
|
|
4011
|
+
const s = p.surface;
|
|
4012
|
+
if (s.detached) continue;
|
|
4013
|
+
this.host().event(s.name, "resize", "", { w: Math.round(s.cols * cellW), h: s.rows * cellH });
|
|
4014
|
+
}
|
|
4015
|
+
}, 100);
|
|
3953
4016
|
}
|
|
3954
4017
|
};
|
|
3955
4018
|
function sequence(chain, f) {
|