@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 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
@@ -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
- this.drag = { pointer: e.pointerId, start: id, target: id, c, r, keys };
2293
- this.emit("dragstart", id, { c, r, keys });
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: an event each time its target changes, and
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
- const [c, r] = this.cellOf(e);
2305
- const target = this.dragTarget(this.elementAt(e, c, r));
2306
- const keys = keysOf(e);
2307
- if (target !== d.target || target === "" && (c !== d.c || r !== d.r)) {
2308
- this.emit("drag", target, { c, r, keys });
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
- this.drag = { pointer: NaN, start: id, target: id, c, r, keys };
2458
- this.emit("dragstart", id, { c, r, keys });
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 (`onDragMove`). */
2506
+ * move (`moveDrag`). */
2466
2507
  touchDragMove(x, y, e) {
2467
2508
  const d = this.drag;
2468
- if (!d) return;
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 within half a
2577
- * device pixel of a cell's is on it: layout rounds positions to fractions
2578
- * of a pixel, and the user sees no less than a device pixel.
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 tol = 0.5 / (this.frame.ownerDocument.defaultView?.devicePixelRatio || 1);
2583
- const c = Math.floor((b.left + tol) / this.cellW);
2584
- const r = Math.floor((b.top + tol) / this.cellH);
2585
- const right = Math.ceil((b.right - tol) / this.cellW);
2586
- const bottom = Math.ceil((b.bottom - tol) / this.cellH);
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 sizeChanged = this.metrics.cellW !== 0 && (cellW !== this.metrics.cellW || cellH !== this.metrics.cellH);
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
- if (sizeChanged) {
3944
- if (this.resizeTimer) clearTimeout(this.resizeTimer);
3945
- this.resizeTimer = setTimeout(() => {
3946
- for (const p of this.placements.values()) {
3947
- const s = p.surface;
3948
- if (s.detached) continue;
3949
- this.host().event(s.name, "resize", "", { w: Math.round(s.cols * cellW), h: s.rows * cellH });
3950
- }
3951
- }, 100);
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) {