@qodalis/cli 2.5.0 → 2.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qodalis/cli",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Framework-agnostic CLI engine for @qodalis terminal applications.",
5
5
  "author": "Nicolae Lupei, Qodalis Solutions",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "./assets/*": "./assets/*"
31
31
  },
32
32
  "dependencies": {
33
- "@qodalis/cli-core": "2.5.0",
33
+ "@qodalis/cli-core": "2.5.1",
34
34
  "@xterm/xterm": "^6.0.0",
35
35
  "@xterm/addon-fit": "^0.11.0",
36
36
  "@xterm/addon-web-links": "^0.12.0",
package/public-api.d.mts CHANGED
@@ -165,7 +165,7 @@ declare const CliKeyValueStore_TOKEN: any;
165
165
  */
166
166
  declare const SyntaxHighlighterRegistry_TOKEN: any;
167
167
 
168
- declare const LIBRARY_VERSION = "2.5.0";
168
+ declare const LIBRARY_VERSION = "2.5.1";
169
169
  declare const API_VERSION = 2;
170
170
 
171
171
  declare class CliAliasCommandProcessor implements ICliCommandProcessor {
@@ -2889,6 +2889,14 @@ interface CliEngineOptions extends CliOptions {
2889
2889
  * @default true
2890
2890
  */
2891
2891
  autoFocus?: boolean;
2892
+ /**
2893
+ * Scroll the scrollback with a finger on touch screens (xterm.js has no
2894
+ * touch scrolling, so a drag would scroll the page instead). Paused while
2895
+ * a full-screen command runs. Turn it off when the host scrolls the
2896
+ * terminal itself.
2897
+ * @default true
2898
+ */
2899
+ touchScroll?: boolean;
2892
2900
  }
2893
2901
  declare class CliEngine {
2894
2902
  private readonly container;
@@ -2903,6 +2911,7 @@ declare class CliEngine {
2903
2911
  private resizeObserver?;
2904
2912
  private resizeListener?;
2905
2913
  private wheelListener?;
2914
+ private disposeTouchScroll?;
2906
2915
  private resizeVersion;
2907
2916
  private resizeScheduled;
2908
2917
  private bootService?;
@@ -4165,6 +4174,11 @@ declare class CliPanelController implements ICliPanelRef<CliEngine> {
4165
4174
  private readonly onSystemSchemeChange;
4166
4175
  /** A terminal in the panel changed theme: follow the first one. */
4167
4176
  private readonly onTerminalTheme;
4177
+ /**
4178
+ * A wheel over the panel chrome (header, icon strip, empty pane) would
4179
+ * scroll the page behind it; the terminals already keep their own wheel.
4180
+ */
4181
+ private readonly onPanelWheel;
4168
4182
  /**
4169
4183
  * Phone browsers open the keyboard over the page and shrink only the visual
4170
4184
  * viewport; follow it so the panel stays above the keyboard.
package/public-api.d.ts CHANGED
@@ -165,7 +165,7 @@ declare const CliKeyValueStore_TOKEN: any;
165
165
  */
166
166
  declare const SyntaxHighlighterRegistry_TOKEN: any;
167
167
 
168
- declare const LIBRARY_VERSION = "2.5.0";
168
+ declare const LIBRARY_VERSION = "2.5.1";
169
169
  declare const API_VERSION = 2;
170
170
 
171
171
  declare class CliAliasCommandProcessor implements ICliCommandProcessor {
@@ -2889,6 +2889,14 @@ interface CliEngineOptions extends CliOptions {
2889
2889
  * @default true
2890
2890
  */
2891
2891
  autoFocus?: boolean;
2892
+ /**
2893
+ * Scroll the scrollback with a finger on touch screens (xterm.js has no
2894
+ * touch scrolling, so a drag would scroll the page instead). Paused while
2895
+ * a full-screen command runs. Turn it off when the host scrolls the
2896
+ * terminal itself.
2897
+ * @default true
2898
+ */
2899
+ touchScroll?: boolean;
2892
2900
  }
2893
2901
  declare class CliEngine {
2894
2902
  private readonly container;
@@ -2903,6 +2911,7 @@ declare class CliEngine {
2903
2911
  private resizeObserver?;
2904
2912
  private resizeListener?;
2905
2913
  private wheelListener?;
2914
+ private disposeTouchScroll?;
2906
2915
  private resizeVersion;
2907
2916
  private resizeScheduled;
2908
2917
  private bootService?;
@@ -4165,6 +4174,11 @@ declare class CliPanelController implements ICliPanelRef<CliEngine> {
4165
4174
  private readonly onSystemSchemeChange;
4166
4175
  /** A terminal in the panel changed theme: follow the first one. */
4167
4176
  private readonly onTerminalTheme;
4177
+ /**
4178
+ * A wheel over the panel chrome (header, icon strip, empty pane) would
4179
+ * scroll the page behind it; the terminals already keep their own wheel.
4180
+ */
4181
+ private readonly onPanelWheel;
4168
4182
  /**
4169
4183
  * Phone browsers open the keyboard over the page and shrink only the visual
4170
4184
  * viewport; follow it so the panel stays above the keyboard.
package/public-api.js CHANGED
@@ -519,7 +519,7 @@ var CliKeyValueStore_TOKEN = cliCore.createCliToken("cli-key-value-store");
519
519
  var SyntaxHighlighterRegistry_TOKEN = cliCore.createCliToken("syntax-highlighter-registry");
520
520
 
521
521
  // src/lib/version.ts
522
- var LIBRARY_VERSION = "2.5.0";
522
+ var LIBRARY_VERSION = "2.5.1";
523
523
  var API_VERSION = 2;
524
524
  var ANONYMOUS = "\0anonymous";
525
525
  function aliasOwner(context2) {
@@ -23865,6 +23865,143 @@ function createCompletionProviders(deps) {
23865
23865
  return [...fromPlugins, ...builtIn];
23866
23866
  }
23867
23867
 
23868
+ // src/lib/engine/cli-touch-scroll.ts
23869
+ var SLOP_PX = 8;
23870
+ var MIN_FLING = 0.15;
23871
+ var FRICTION = 0.997;
23872
+ var VELOCITY_WINDOW_MS = 100;
23873
+ var KEYBOARD_HOLD_MS = 400;
23874
+ function enableTouchScroll(terminal, paused = () => false) {
23875
+ const element = terminal.element;
23876
+ if (!element) {
23877
+ return () => {
23878
+ };
23879
+ }
23880
+ let startY = 0;
23881
+ let lastY = 0;
23882
+ let scrolling = false;
23883
+ let carry = 0;
23884
+ let moves = [];
23885
+ let frame = 0;
23886
+ let holdTimer = 0;
23887
+ let holding = false;
23888
+ const textarea = terminal.textarea;
23889
+ const keyboardOpen = () => window.innerHeight < screen.height * 0.75;
23890
+ const holdKeyboard = () => {
23891
+ clearTimeout(holdTimer);
23892
+ if (holding || !textarea || textarea.hasAttribute("inputmode") || keyboardOpen()) {
23893
+ return;
23894
+ }
23895
+ holding = true;
23896
+ textarea.setAttribute("inputmode", "none");
23897
+ };
23898
+ const releaseKeyboard = () => {
23899
+ clearTimeout(holdTimer);
23900
+ if (holding) {
23901
+ holding = false;
23902
+ textarea?.removeAttribute("inputmode");
23903
+ }
23904
+ };
23905
+ const rowHeight = () => {
23906
+ const screen2 = element.querySelector(".xterm-screen");
23907
+ return (screen2?.clientHeight || element.clientHeight) / terminal.rows || 16;
23908
+ };
23909
+ const scrollBy = (pixels) => {
23910
+ carry += pixels;
23911
+ const height = rowHeight();
23912
+ const rows = Math.trunc(carry / height);
23913
+ if (rows === 0) {
23914
+ return true;
23915
+ }
23916
+ carry -= rows * height;
23917
+ const buffer = terminal.buffer.active;
23918
+ const before = buffer.viewportY;
23919
+ terminal.scrollLines(-rows);
23920
+ const moved = buffer.viewportY !== before;
23921
+ if (!moved) {
23922
+ carry = 0;
23923
+ }
23924
+ return moved;
23925
+ };
23926
+ const stopGlide = () => {
23927
+ cancelAnimationFrame(frame);
23928
+ frame = 0;
23929
+ };
23930
+ const glide = (velocity) => {
23931
+ let previous = performance.now();
23932
+ const step = (now) => {
23933
+ const elapsed = Math.min(now - previous, 50);
23934
+ previous = now;
23935
+ velocity *= Math.pow(FRICTION, elapsed);
23936
+ if (Math.abs(velocity) < 0.02 || !scrollBy(velocity * elapsed)) {
23937
+ frame = 0;
23938
+ return;
23939
+ }
23940
+ frame = requestAnimationFrame(step);
23941
+ };
23942
+ frame = requestAnimationFrame(step);
23943
+ };
23944
+ const onStart = (e) => {
23945
+ stopGlide();
23946
+ scrolling = false;
23947
+ if (e.touches.length !== 1 || paused()) {
23948
+ return;
23949
+ }
23950
+ holdKeyboard();
23951
+ startY = lastY = e.touches[0].clientY;
23952
+ carry = 0;
23953
+ moves = [{ y: startY, time: e.timeStamp }];
23954
+ };
23955
+ const onMove = (e) => {
23956
+ if (e.touches.length !== 1 || paused() || moves.length === 0) {
23957
+ return;
23958
+ }
23959
+ const y = e.touches[0].clientY;
23960
+ if (!scrolling && Math.abs(y - startY) < SLOP_PX) {
23961
+ return;
23962
+ }
23963
+ scrolling = true;
23964
+ e.preventDefault();
23965
+ scrollBy(y - lastY);
23966
+ lastY = y;
23967
+ moves.push({ y, time: e.timeStamp });
23968
+ while (moves.length > 2 && e.timeStamp - moves[0].time > VELOCITY_WINDOW_MS) {
23969
+ moves.shift();
23970
+ }
23971
+ };
23972
+ const onEnd = (e) => {
23973
+ if (!scrolling) {
23974
+ releaseKeyboard();
23975
+ moves = [];
23976
+ return;
23977
+ }
23978
+ e.preventDefault();
23979
+ scrolling = false;
23980
+ holdTimer = window.setTimeout(releaseKeyboard, KEYBOARD_HOLD_MS);
23981
+ const first = moves[0];
23982
+ const last = moves[moves.length - 1];
23983
+ moves = [];
23984
+ const elapsed = last.time - first.time;
23985
+ const velocity = elapsed > 0 && e.timeStamp - last.time < VELOCITY_WINDOW_MS ? (last.y - first.y) / elapsed : 0;
23986
+ if (Math.abs(velocity) >= MIN_FLING) {
23987
+ glide(velocity);
23988
+ }
23989
+ };
23990
+ const options = { passive: false, capture: true };
23991
+ element.addEventListener("touchstart", onStart, options);
23992
+ element.addEventListener("touchmove", onMove, options);
23993
+ element.addEventListener("touchend", onEnd, options);
23994
+ element.addEventListener("touchcancel", onEnd, options);
23995
+ return () => {
23996
+ stopGlide();
23997
+ releaseKeyboard();
23998
+ element.removeEventListener("touchstart", onStart, options);
23999
+ element.removeEventListener("touchmove", onMove, options);
24000
+ element.removeEventListener("touchend", onEnd, options);
24001
+ element.removeEventListener("touchcancel", onEnd, options);
24002
+ };
24003
+ }
24004
+
23868
24005
  // src/lib/engine/cli-engine.ts
23869
24006
  var CliEngine = class {
23870
24007
  constructor(container, options) {
@@ -23976,6 +24113,12 @@ var CliEngine = class {
23976
24113
  this.serializeAddon = setup.serializeAddon;
23977
24114
  this.dragDropService = setup.dragDropService;
23978
24115
  this.wheelListener = setup.wheelListener;
24116
+ if (this.options?.touchScroll !== false) {
24117
+ this.disposeTouchScroll = enableTouchScroll(
24118
+ this.terminal,
24119
+ () => !!this.executionContext?.contextProcessor
24120
+ );
24121
+ }
23979
24122
  this.handleResize();
23980
24123
  }
23981
24124
  /** 2. Service container, boot service, executor and execution context. */
@@ -24205,6 +24348,7 @@ var CliEngine = class {
24205
24348
  if (this.wheelListener) {
24206
24349
  this.container.removeEventListener("wheel", this.wheelListener);
24207
24350
  }
24351
+ this.disposeTouchScroll?.();
24208
24352
  this.resizeVersion = -1;
24209
24353
  this.resizeScheduled = false;
24210
24354
  this.resizeObserver?.disconnect();
@@ -25113,6 +25257,21 @@ var DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
25113
25257
  function asEventTarget(node) {
25114
25258
  return node && typeof node.addEventListener === "function" ? node : null;
25115
25259
  }
25260
+ function canScrollWithin(target, root, dx, dy) {
25261
+ for (let el = target; el && el !== root; el = el.parentElement) {
25262
+ if (!(el instanceof HTMLElement)) continue;
25263
+ const style = getComputedStyle(el);
25264
+ const scrollsY = /auto|scroll/.test(style.overflowY) && el.scrollHeight > el.clientHeight;
25265
+ const scrollsX = /auto|scroll/.test(style.overflowX) && el.scrollWidth > el.clientWidth;
25266
+ if (dy !== 0 && scrollsY) {
25267
+ if (dy < 0 ? el.scrollTop > 0 : el.scrollTop + el.clientHeight < el.scrollHeight) return true;
25268
+ }
25269
+ if (dx !== 0 && scrollsX) {
25270
+ if (dx < 0 ? el.scrollLeft > 0 : el.scrollLeft + el.clientWidth < el.scrollWidth) return true;
25271
+ }
25272
+ }
25273
+ return false;
25274
+ }
25116
25275
  function followsSystem(input) {
25117
25276
  return input === "auto" || typeof input === "object" && input !== null && input.base === "auto";
25118
25277
  }
@@ -25194,6 +25353,16 @@ var CliPanelController = class {
25194
25353
  if (event.target !== first) return;
25195
25354
  this.syncThemeFromEngine(event.detail);
25196
25355
  };
25356
+ /**
25357
+ * A wheel over the panel chrome (header, icon strip, empty pane) would
25358
+ * scroll the page behind it; the terminals already keep their own wheel.
25359
+ */
25360
+ this.onPanelWheel = (event) => {
25361
+ const wheel = event;
25362
+ const root = this.rootElement;
25363
+ if (!root || canScrollWithin(wheel.target, root, wheel.deltaX, wheel.deltaY)) return;
25364
+ wheel.preventDefault();
25365
+ };
25197
25366
  this.onVisualViewportChange = () => {
25198
25367
  const fields = visualViewportFields();
25199
25368
  const s = this.snapshot;
@@ -25302,9 +25471,13 @@ var CliPanelController = class {
25302
25471
  /** Element containing the terminals, used for theme sync. */
25303
25472
  setRootElement(element) {
25304
25473
  if (element === this.rootElement) return;
25305
- asEventTarget(this.rootElement)?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25474
+ const previous = asEventTarget(this.rootElement);
25475
+ previous?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25476
+ previous?.removeEventListener("wheel", this.onPanelWheel);
25306
25477
  this.rootElement = element;
25307
- asEventTarget(element)?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25478
+ const next = asEventTarget(element);
25479
+ next?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25480
+ next?.addEventListener("wheel", this.onPanelWheel, { passive: false });
25308
25481
  if (element && this.followsTerminalTheme) this.startThemeSync();
25309
25482
  }
25310
25483
  /**