@neuroplastio/xterm-addon-hotty 0.1.0-next.1 → 0.1.0-next.3

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
@@ -171,11 +171,33 @@ runs programs for whoever connects.
171
171
  lose the capture. The browser clicks the root on a captured release, so
172
172
  the drag reports the click itself when it ends where it began. The host
173
173
  stylesheet makes the elements that opt in unselectable, important in its
174
- layer, whatever the document's CSS. A touch never drags.
174
+ layer, whatever the document's CSS.
175
+
176
+ A touch drags such an element only where its `touch-action` allows no
177
+ pan along the touch's first move past 8 px (the larger delta; a tie is a
178
+ pan). The value is the element's own, met with its ancestors' up to the
179
+ nearest one that scrolls, both included, as Pointer Events have it; the
180
+ host's own `touch-action` on the root (below) is not the document's. The
181
+ touch then reports `press` and `dragstart` at the cell where it began,
182
+ and drags from there as a mouse does; the lift reports `dragend`, and the
183
+ click where it began. A second finger ends it with `dragend` and no
184
+ target, and nothing more is heard until every finger lifts. A long press,
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`.
175
196
  - **Presses** (SPEC §5.2, §9: `p=1` on `a=place`). Every primary press of a
176
197
  mouse or a pen in the window reports `press` on its pointerdown, before
177
198
  the drag and the focus it causes; a tap reports it on the mousedown the
178
- browser makes for it. A press on a hyperlink is the terminal's.
199
+ browser makes for it, and a touch that drags when it becomes a drag. A
200
+ touch that pans presses nothing. A press on a hyperlink is the terminal's.
179
201
  - **Fit** (SPEC §5.2, §9: `f=1` on `a=place`). The program hears `fit`
180
202
  with the rows the document needs at the placement's width whenever they
181
203
  differ from the rows it heard last, starting from the placement's own;
@@ -232,8 +254,8 @@ runs programs for whoever connects.
232
254
  - It shows no scrollbars, and pans nothing on a touch
233
255
  (`touch-action: none`). Any scroll offset the browser sets goes back to
234
256
  zero, except a text field's own text.
235
- - A wheel over a surface goes to the terminal, and so does a touch drag,
236
- as wheel events at the finger. After the finger lifts, the drag keeps
257
+ - A wheel over a surface goes to the terminal, and so does a touch that
258
+ pans, as wheel events at the finger. After the finger lifts, the drag keeps
237
259
  going and slows down.
238
260
  - Taps and long presses stay the surface's.
239
261
  - On the cells, the addon handles touch too (the `touch` option, on by
@@ -280,9 +302,12 @@ runs programs for whoever connects.
280
302
  the pointer that can move that way can (the browser scrolls it), else
281
303
  on to the terminal, as over the cells, unless `overscroll-behavior`
282
304
  stops it. A gesture begun over the cells stays the terminal's when a
283
- surface comes under the pointer. A touch drag decides the same way once
284
- it has a direction: the browser pans the document, or the drag goes to
285
- the terminal as before.
305
+ surface comes under the pointer. A touch that pans decides the same way
306
+ once it has a direction: the browser pans the document, or the touch
307
+ goes to the terminal as before. Where the document's `touch-action`
308
+ would stop the browser's pan (a `none` on a box that doesn't drag), the
309
+ addon pans the box itself, so that only a drag element takes a touch
310
+ from scrolling (SPEC §9.1).
286
311
  - **Keys.** While the surface has the keyboard, the keys a browser
287
312
  scrolls with that the focused element neither uses nor gives the
288
313
  program (SPEC §10.2) scroll the innermost box, from the focused
@@ -67,14 +67,13 @@ function scrollCss(axes, pageScrolls) {
67
67
  if (!axes) {
68
68
  return [
69
69
  "@layer hotty-host {",
70
- `:root { overflow: hidden; touch-action: ${pageScrolls ? "manipulation" : "none"}; }`,
70
+ `:root { overflow: hidden; touch-action: ${rootTouchAction(axes, pageScrolls)}; }`,
71
71
  "* { scrollbar-width: none !important; }",
72
72
  "::-webkit-scrollbar { display: none !important; }",
73
73
  "}"
74
74
  ].join("\n");
75
75
  }
76
- const pan = pageScrolls ? "manipulation" : axes === 3 ? "pan-x pan-y" : axes === 1 ? "pan-y" : "pan-x";
77
- const lines = ["@layer hotty-host {", `:root { touch-action: ${pan}; }`];
76
+ const lines = ["@layer hotty-host {", `:root { touch-action: ${rootTouchAction(axes, pageScrolls)}; }`];
78
77
  if (axes !== 3) {
79
78
  const clipped = axes === 1 ? "overflow-x" : "overflow-y";
80
79
  lines.push(`:root, :root:not([${UNCLIPPED}]) [${CLIP}] { ${clipped}: hidden !important; }`);
@@ -82,6 +81,10 @@ function scrollCss(axes, pageScrolls) {
82
81
  lines.push("}");
83
82
  return lines.join("\n");
84
83
  }
84
+ function rootTouchAction(axes, pageScrolls) {
85
+ if (pageScrolls) return "manipulation";
86
+ return axes === 3 ? "pan-x pan-y" : axes === 1 ? "pan-y" : axes === 2 ? "pan-x" : "none";
87
+ }
85
88
  function fontStack(family) {
86
89
  const f = family.trim();
87
90
  if (!f) return "monospace";
@@ -515,9 +518,27 @@ var Store = class {
515
518
 
516
519
  // src/touch.ts
517
520
  var DRAG_SLOP = 8;
521
+ var LONG_PRESS_MS = 500;
518
522
  var FLING_DECAY = 0.996;
519
523
  var REST_MS = 80;
524
+ function touchPans(value) {
525
+ const v = value.trim();
526
+ if (!v || v === "auto" || v === "manipulation") return [true, true];
527
+ const words = v.split(/\s+/);
528
+ return [words.some((w) => /^pan-(x|left|right)$/.test(w)), words.some((w) => /^pan-(y|up|down)$/.test(w))];
529
+ }
530
+ function panBlocked(pans, dx, dy) {
531
+ const ax = Math.abs(dx), ay = Math.abs(dy);
532
+ return ax > ay ? !pans.x : ay > ax ? !pans.y : false;
533
+ }
520
534
  var Touch = class {
535
+ touch = null;
536
+ fling = 0;
537
+ off = [];
538
+ win;
539
+ host;
540
+ exclusive;
541
+ engage;
521
542
  /**
522
543
  * `exclusive`: every touch here is this Touch's (the cells: xterm.js's own
523
544
  * touch handling, which listens on the document, never sees it). Otherwise
@@ -537,19 +558,12 @@ var Touch = class {
537
558
  };
538
559
  on("touchstart", (e) => this.start(e), true);
539
560
  on("touchmove", (e) => this.move(e), false);
540
- on("touchend", (e) => this.end(e), !exclusive);
561
+ on("touchend", (e) => this.end(e), false);
541
562
  on("touchcancel", (e) => {
542
563
  this.keep(e);
543
- this.touch = null;
564
+ this.drop();
544
565
  }, true);
545
566
  }
546
- win;
547
- host;
548
- exclusive;
549
- engage;
550
- touch = null;
551
- fling = 0;
552
- off = [];
553
567
  dispose() {
554
568
  this.stopFling();
555
569
  for (const f of this.off) f();
@@ -557,32 +571,62 @@ var Touch = class {
557
571
  keep(e) {
558
572
  if (this.exclusive) e.stopPropagation();
559
573
  }
574
+ /** The touch under way ends without its lift: a drag the host holds
575
+ * hears it. */
576
+ drop() {
577
+ const s = this.touch;
578
+ this.touch = null;
579
+ if (s?.held) this.host.dragCancel?.();
580
+ }
560
581
  start(e) {
561
582
  this.keep(e);
562
583
  this.stopFling();
563
584
  const t = e.touches.length === 1 ? e.touches[0] : void 0;
564
585
  if (!t || this.engage && !this.engage(e)) {
565
- this.touch = null;
586
+ this.drop();
566
587
  return;
567
588
  }
568
589
  const [x, y] = this.host.toPage(t.clientX, t.clientY);
569
- this.touch = { id: t.identifier, x0: x, y0: y, x, y, t: e.timeStamp, t0: e.timeStamp, vx: 0, vy: 0, dragging: false };
590
+ const begin = {
591
+ target: e.target,
592
+ x: t.clientX,
593
+ y: t.clientY,
594
+ time: e.timeStamp,
595
+ shiftKey: e.shiftKey,
596
+ ctrlKey: e.ctrlKey,
597
+ altKey: e.altKey,
598
+ metaKey: e.metaKey
599
+ };
600
+ this.touch = { id: t.identifier, x0: x, y0: y, x, y, t: e.timeStamp, t0: e.timeStamp, vx: 0, vy: 0, dragging: false, held: false, begin };
570
601
  }
571
602
  move(e) {
572
603
  this.keep(e);
573
604
  const s = this.touch;
574
605
  if (!s) return;
575
606
  if (e.touches.length !== 1) {
576
- this.touch = null;
607
+ this.drop();
577
608
  return;
578
609
  }
579
610
  const t = [...e.changedTouches].find((c) => c.identifier === s.id);
580
611
  if (!t) return;
612
+ if (s.held) {
613
+ e.preventDefault();
614
+ this.host.dragMove?.(t.clientX, t.clientY, e);
615
+ return;
616
+ }
581
617
  const [x, y] = this.host.toPage(t.clientX, t.clientY);
582
618
  if (!s.dragging && Math.hypot(x - s.x0, y - s.y0) < DRAG_SLOP) return;
583
- if (!s.dragging && this.host.claim && !this.host.claim(s.x0 - x, s.y0 - y, e)) {
584
- this.touch = null;
585
- return;
619
+ if (!s.dragging && this.host.claim) {
620
+ const claimed = this.host.claim(s.x0 - x, s.y0 - y, e, s.begin);
621
+ if (!claimed) {
622
+ this.touch = null;
623
+ return;
624
+ }
625
+ if (claimed === "drag") {
626
+ s.held = true;
627
+ e.preventDefault();
628
+ return;
629
+ }
586
630
  }
587
631
  s.dragging = true;
588
632
  e.preventDefault();
@@ -599,13 +643,19 @@ var Touch = class {
599
643
  end(e) {
600
644
  this.keep(e);
601
645
  const s = this.touch;
602
- if (!s || ![...e.changedTouches].some((c) => c.identifier === s.id)) return;
646
+ const t = s && [...e.changedTouches].find((c) => c.identifier === s.id);
647
+ if (!s || !t) return;
603
648
  this.touch = null;
649
+ if (s.held) {
650
+ e.preventDefault();
651
+ this.host.dragEnd?.(t.clientX, t.clientY, e);
652
+ return;
653
+ }
604
654
  if (s.dragging) {
605
655
  if (e.timeStamp - s.t < REST_MS) this.startFling(-s.vx, -s.vy, s.x, s.y);
606
656
  return;
607
657
  }
608
- if (this.host.tap && e.timeStamp - s.t0 < 500) {
658
+ if (this.host.tap && e.timeStamp - s.t0 < LONG_PRESS_MS) {
609
659
  e.preventDefault();
610
660
  this.host.tap(s.x, s.y);
611
661
  }
@@ -633,6 +683,21 @@ var Touch = class {
633
683
  }
634
684
  };
635
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
+
636
701
  // src/resolver.ts
637
702
  var DROP = /* @__PURE__ */ new Set(["script", "iframe", "frame", "frameset", "object", "embed", "applet", "base", "meta", "portal", "fencedframe"]);
638
703
  var DROP_ATTRS = /* @__PURE__ */ new Set(["srcdoc", "autofocus", "http-equiv", "ping", "nonce", "integrity"]);
@@ -1371,8 +1436,13 @@ var Surface = class {
1371
1436
  /** The axes the document asked to scroll along (`scroll`, SPEC §5.1): 1
1372
1437
  * vertically, 2 horizontally, 3 both; 0, nothing scrolls. */
1373
1438
  axes = 0;
1374
- /** Where the touch drag under way goes, once it has a direction. */
1439
+ /** Where the touch drag under way goes, once it has a direction: "self",
1440
+ * the document, but scrolled by the surface, since the document's
1441
+ * `touch-action` would stop the browser's own pan (SPEC §9.1: it changes
1442
+ * nothing there). */
1375
1443
  touchRoute = "terminal";
1444
+ /** The box a touch drag scrolls itself ("self"). */
1445
+ touchBox = null;
1376
1446
  /** The box the wheel gesture under way scrolls, if it went to the
1377
1447
  * document: the gesture moves no other (`scrollWheel`). */
1378
1448
  wheelBox = null;
@@ -1727,6 +1797,11 @@ var Surface = class {
1727
1797
  setTimeout(() => this.auxPress = false, 0);
1728
1798
  return;
1729
1799
  }
1800
+ this.pressAt(e);
1801
+ }
1802
+ /** What a click (SPEC §10.1) at `e`'s target does to the keyboard: a
1803
+ * press of the primary button, a tap, or a touch that becomes a drag. */
1804
+ pressAt(e) {
1730
1805
  const el = this.detachedState ? null : focusTargetAt(e, this.hyper);
1731
1806
  if (el) {
1732
1807
  this.pressed = el;
@@ -2229,25 +2304,49 @@ var Surface = class {
2229
2304
  if (!id) return;
2230
2305
  const [c, r] = this.cellOf(e);
2231
2306
  const keys = keysOf(e);
2232
- this.drag = { pointer: e.pointerId, start: id, target: id, c, r, keys };
2233
- 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 });
2234
2311
  try {
2235
2312
  this.doc.documentElement.setPointerCapture(e.pointerId);
2236
2313
  } catch {
2237
2314
  }
2238
2315
  }
2239
- /** A move during a drag: an event each time its target changes, and
2240
- * while it has none, each time its cell does. */
2316
+ /** A move during a drag (`moveDrag`). */
2241
2317
  onDragMove(e) {
2242
2318
  const d = this.drag;
2243
2319
  if (!d || e.pointerId !== d.pointer) return;
2244
- const [c, r] = this.cellOf(e);
2245
- const target = this.dragTarget(this.elementAt(e, c, r));
2246
- const keys = keysOf(e);
2247
- if (target !== d.target || target === "" && (c !== d.c || r !== d.r)) {
2248
- 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 });
2249
2332
  }
2250
- 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;
2251
2350
  }
2252
2351
  /** The release ends the drag, wherever it is. Where it began, it is the
2253
2352
  * click it would have been without the drag; anywhere else, no click. */
@@ -2258,7 +2357,7 @@ var Surface = class {
2258
2357
  const [c, r] = this.cellOf(e);
2259
2358
  const at = this.elementAt(e, c, r);
2260
2359
  const target = this.dragTarget(at);
2261
- 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) });
2262
2361
  this.dragReleased = true;
2263
2362
  setTimeout(() => this.dragReleased = false, 0);
2264
2363
  if (target === d.start && at) this.reportClick(ancestry(at));
@@ -2271,13 +2370,13 @@ var Surface = class {
2271
2370
  /**
2272
2371
  * Ends a drag under way without a release (SPEC §9.1: its placement went
2273
2372
  * away, a new document came, or the pointer was lost): `dragend` with no
2274
- * target, at the last cell the program heard of.
2373
+ * target, at the last cell and step the program heard of.
2275
2374
  */
2276
2375
  cancelDrag() {
2277
2376
  const d = this.drag;
2278
2377
  if (!d) return;
2279
2378
  this.dropDrag();
2280
- 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 });
2281
2380
  }
2282
2381
  /** Ends a drag under way, silently (a detached or deleted surface). */
2283
2382
  dropDrag() {
@@ -2292,13 +2391,11 @@ var Surface = class {
2292
2391
  /** The surface's cell under the pointer, from its top left (the frame's
2293
2392
  * origin), counting on past its edges. */
2294
2393
  cellOf(e) {
2295
- return [Math.floor(e.clientX / this.cellW), Math.floor(e.clientY / this.cellH)];
2394
+ return this.cellAt(e.clientX, e.clientY);
2296
2395
  }
2297
2396
  /** The element under the pointer, if it is in the window (SPEC §5.2). */
2298
2397
  elementAt(e, c, r) {
2299
- const w = this.win;
2300
- if (c < w.x || c >= w.x + w.w || r < w.y || r >= w.y + w.h) return null;
2301
- return this.doc.elementFromPoint(e.clientX, e.clientY);
2398
+ return this.elementAtPoint(e.clientX, e.clientY, c, r);
2302
2399
  }
2303
2400
  /** A drag's target: the nearest element with an id and `drag` in its
2304
2401
  * `data-on`, from `el` outward; "" where there is none. */
@@ -2309,6 +2406,134 @@ var Surface = class {
2309
2406
  }
2310
2407
  return "";
2311
2408
  }
2409
+ // --- Touch drags (SPEC §9.1) ----------------------------------------------
2410
+ /**
2411
+ * Where a touch that has just gone past the slop, scrolling by dx, dy so
2412
+ * far, goes. A drag, where the touched element opts in and the
2413
+ * touch-action that counts for it allows no pan along the touch's first
2414
+ * move (the larger delta; a tie pans). Otherwise a pan, as §5.3 and §9
2415
+ * have it, whatever touch-action says: where the browser would pan the
2416
+ * document but the document's touch-action stops it, the surface scrolls
2417
+ * the box itself.
2418
+ */
2419
+ claimTouch(dx, dy, e, begin) {
2420
+ const el = begin.target && isElement(begin.target) ? begin.target : null;
2421
+ const pans = this.allowedPans(el);
2422
+ if (panBlocked(pans, dx, dy) && this.touchDragStart(e, begin)) return "drag";
2423
+ if (!this.axes) {
2424
+ this.touchRoute = "terminal";
2425
+ return true;
2426
+ }
2427
+ const to = this.gestureTarget(el, dx, dy);
2428
+ if (typeof to === "string") {
2429
+ this.touchRoute = to;
2430
+ return true;
2431
+ }
2432
+ if (pans.x && pans.y) {
2433
+ this.touchRoute = "doc";
2434
+ return false;
2435
+ }
2436
+ this.touchRoute = "self";
2437
+ this.touchBox = to;
2438
+ return true;
2439
+ }
2440
+ /** Whether the touch-action that counts at `target` allows every pan. */
2441
+ pansFreely(target) {
2442
+ const p = this.allowedPans(target && isElement(target) ? target : null);
2443
+ return p.x && p.y;
2444
+ }
2445
+ /**
2446
+ * The pans the document's touch-action allows a touch on `el`, as Pointer
2447
+ * Events determine them: its own, with its ancestors' up to the nearest
2448
+ * element that scrolls (SPEC §5.3), both included. The root's is the
2449
+ * document's only where it differs from the host's (`rootTouchAction`),
2450
+ * which is there for the browser's own panning.
2451
+ */
2452
+ allowedPans(el) {
2453
+ let x = true;
2454
+ let y = true;
2455
+ const root = this.doc.documentElement;
2456
+ const host = rootTouchAction(this.axes, this.host.pageScrolls);
2457
+ for (let n = el; n; n = n.parentElement) {
2458
+ const s = n.ownerDocument.defaultView.getComputedStyle(n);
2459
+ const value = n === root && s.touchAction === host ? "auto" : s.touchAction;
2460
+ const [px, py] = touchPans(value);
2461
+ x &&= px;
2462
+ y &&= py;
2463
+ if (n !== root && this.scrolls(s)) break;
2464
+ }
2465
+ return { x, y };
2466
+ }
2467
+ /** Whether an element with this style scrolls (SPEC §5.3): its overflow is
2468
+ * `auto` or `scroll` along an axis the document asked to scroll. */
2469
+ scrolls(s) {
2470
+ return !!(this.axes & 1) && /auto|scroll/.test(s.overflowY) || !!(this.axes & 2) && /auto|scroll/.test(s.overflowX);
2471
+ }
2472
+ /**
2473
+ * A touch becomes a drag (SPEC §9.1), if it can: one that began without
2474
+ * Alt and was not held still past a long press, on an element that opts
2475
+ * in and has an id, in a surface not detached. It presses at this moment
2476
+ * as a mouse does: `press`, then `dragstart`, both for where it began,
2477
+ * then what the press causes; then a `drag` if the finger is by now over
2478
+ * another element.
2479
+ */
2480
+ touchDragStart(e, begin) {
2481
+ if (this.detachedState || begin.altKey || e.timeStamp - begin.time >= LONG_PRESS_MS) return false;
2482
+ const el = begin.target && isElement(begin.target) ? begin.target : null;
2483
+ let start = null;
2484
+ for (let n = el; n; n = n.parentElement) {
2485
+ if (listens(n, "drag")) {
2486
+ start = n;
2487
+ break;
2488
+ }
2489
+ }
2490
+ const id = start?.getAttribute("id");
2491
+ if (!id) return false;
2492
+ this.cancelDrag();
2493
+ const [c, r] = this.cellAt(begin.x, begin.y);
2494
+ const keys = keysOf(begin);
2495
+ this.reportPress(e);
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 });
2500
+ this.pressAt(e);
2501
+ const t = [...e.changedTouches][0];
2502
+ if (t) this.touchDragMove(t.clientX, t.clientY, e);
2503
+ return true;
2504
+ }
2505
+ /** A touch drag's finger moved to (x, y) of the document: as a pointer's
2506
+ * move (`moveDrag`). */
2507
+ touchDragMove(x, y, e) {
2508
+ const d = this.drag;
2509
+ if (d) this.moveDrag(d, x, y, keysOf(e));
2510
+ }
2511
+ /** A touch drag's finger lifted at (x, y): as a pointer's release
2512
+ * (`onDragUp`), a click where it began. */
2513
+ touchDragEnd(x, y, e) {
2514
+ const d = this.drag;
2515
+ if (!d) return;
2516
+ this.drag = null;
2517
+ const [c, r] = this.cellAt(x, y);
2518
+ const at = this.elementAtPoint(x, y, c, r);
2519
+ const target = this.dragTarget(at);
2520
+ this.emit("dragend", target, { c, r, keys: keysOf(e), ...this.stepIn(d, x, y) });
2521
+ this.dragReleased = true;
2522
+ setTimeout(() => this.dragReleased = false, 0);
2523
+ if (target === d.start && at) this.reportClick(ancestry(at));
2524
+ }
2525
+ /** The surface's cell at (x, y) of the document, counting on past its
2526
+ * edges. */
2527
+ cellAt(x, y) {
2528
+ return [Math.floor(x / this.cellW), Math.floor(y / this.cellH)];
2529
+ }
2530
+ /** The element at (x, y) of the document, if cell (c, r) is in the
2531
+ * window (SPEC §5.2). */
2532
+ elementAtPoint(x, y, c, r) {
2533
+ const w = this.win;
2534
+ if (c < w.x || c >= w.x + w.w || r < w.y || r >= w.y + w.h) return null;
2535
+ return this.doc.elementFromPoint(x, y);
2536
+ }
2312
2537
  // --- Presses with Alt (SPEC §9.2) ---------------------------------------
2313
2538
  /**
2314
2539
  * A press of a mouse's or a pen's primary button with Alt held is the
@@ -2455,26 +2680,27 @@ var Surface = class {
2455
2680
  const r = this.frame.getBoundingClientRect();
2456
2681
  return [r.left + x, r.top + y];
2457
2682
  };
2458
- if (this.host.pageScrolls) {
2459
- new Touch(
2460
- d,
2461
- this.frame.ownerDocument.defaultView,
2462
- { scroll: (dx, dy) => this.host.scrollPage(dx, dy), toPage },
2463
- false,
2464
- (e) => !this.axes && this.inScroller(e.target)
2465
- );
2466
- } else {
2467
- new Touch(d, this.frame.ownerDocument.defaultView, {
2683
+ new Touch(
2684
+ d,
2685
+ this.frame.ownerDocument.defaultView,
2686
+ {
2468
2687
  scroll: (dx, dy, x, y) => {
2469
- if (this.touchRoute === "terminal") this.host.wheel(new WheelEvent("wheel", { deltaX: dx, deltaY: dy, deltaMode: 0, clientX: x, clientY: y }), x, y);
2688
+ if (this.touchRoute === "self") this.touchBox?.scrollBy(dx, dy);
2689
+ else if (this.touchRoute !== "terminal") return;
2690
+ else if (this.host.pageScrolls) this.host.scrollPage(dx, dy);
2691
+ else this.host.wheel(new WheelEvent("wheel", { deltaX: dx, deltaY: dy, deltaMode: 0, clientX: x, clientY: y }), x, y);
2470
2692
  },
2471
2693
  toPage,
2472
- claim: (dx, dy, e) => {
2473
- this.touchRoute = this.axes ? this.gestureRoute(e.target, dx, dy) : "terminal";
2474
- return this.touchRoute !== "doc";
2475
- }
2476
- });
2477
- }
2694
+ claim: (dx, dy, e, begin) => this.claimTouch(dx, dy, e, begin),
2695
+ dragMove: (x, y, e) => this.touchDragMove(x, y, e),
2696
+ dragEnd: (x, y, e) => this.touchDragEnd(x, y, e),
2697
+ dragCancel: () => this.cancelDrag()
2698
+ },
2699
+ false,
2700
+ // Where the page scrolls, the browser pans it from a touch the
2701
+ // surface leaves alone.
2702
+ this.host.pageScrolls ? (e) => !this.axes && this.inScroller(e.target) || !this.pansFreely(e.target) : void 0
2703
+ );
2478
2704
  win.addEventListener("focus", () => {
2479
2705
  if (!this.programFocus && !this.auxPress && !this.keyboard) this.settle();
2480
2706
  });
@@ -2781,6 +3007,10 @@ function isElement(n) {
2781
3007
  function listens(el, what) {
2782
3008
  return (el.getAttribute("data-on") ?? "").split(/\s+/).includes(what);
2783
3009
  }
3010
+ function stepsOf(el) {
3011
+ const n = parseSteps(el.getAttribute("data-steps"));
3012
+ return n && { el, x: n[0], y: n[1] };
3013
+ }
2784
3014
  function ancestry(el) {
2785
3015
  const out = [];
2786
3016
  for (let n = el; n; n = n.parentElement) out.push(n);
@@ -3324,6 +3554,7 @@ var HottyAddon = class {
3324
3554
  limits: { resources: this.store.quota, surfaces: this.opts.maxSurfaces },
3325
3555
  net: this.policy,
3326
3556
  scroll: true,
3557
+ steps: true,
3327
3558
  host: HOST,
3328
3559
  version: VERSION
3329
3560
  };