@ai-matrx/terminal 0.0.0-stage → 0.1.2
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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +52 -2
- package/dist/index.cjs +567 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +544 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +849 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +95 -0
- package/dist/react.d.ts +95 -0
- package/dist/react.js +817 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +148 -0
- package/dist/tokens.css +25 -0
- package/package.json +97 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/keys.ts","../src/core/modifiers.ts","../src/core/gesture.ts","../src/core/selection.ts","../src/core/viewport.ts","../src/core/theme.ts","../src/core/line-editor.ts"],"sourcesContent":["/**\n * @ai-matrx/terminal — framework-free core: accessory keys and their byte sequences, the\n * Ctrl / Alt latch, gesture recognition, selection math, viewport math and the terminal look.\n * React lives in `@ai-matrx/terminal/react`; structure in `./styles.css`; default colours in\n * `./tokens.css`.\n */\nexport { ACCESSORY_KEYS, applyModifiers, controlCode, keySequence } from \"./core/keys\";\nexport type { AccessoryKeyDef, AccessoryKeyId, ActiveModifiers, KeyModes } from \"./core/keys\";\nexport { DOUBLE_TAP_MS, MODIFIER_OFF, afterKey, isActive, tapModifier } from \"./core/modifiers\";\nexport type { ModifierLatch, ModifierState } from \"./core/modifiers\";\nexport { FLING_FRICTION, FLING_STOP, LONG_PRESS_MS, createGestureRecognizer, flingStep } from \"./core/gesture\";\nexport type { GestureAction, GestureOptions, GestureRecognizer, GestureSample } from \"./core/gesture\";\nexport { cellAt, spanBetween, wordBounds } from \"./core/selection\";\nexport type { Cell } from \"./core/selection\";\nexport { KEYBOARD_MIN_PX, keyboardInset, visibleHeightBelow } from \"./core/viewport\";\nexport type { ViewportBox } from \"./core/viewport\";\nexport { ACCESSORY_BAR_HEIGHT, FONT_SIZE_MAX, FONT_SIZE_MIN, TERMINAL_DEFAULTS, TERMINAL_FONT_FAMILY, pinchFontSize, stepFontSize, terminalTheme } from \"./core/theme\";\nexport { createLineEditor } from \"./core/line-editor\";\nexport type { LineEditor, LineEditorHost } from \"./core/line-editor\";\n","/**\n * The keyboard accessory bar's keys and the bytes each sends — the keys a phone keyboard lacks.\n * Pure: no DOM, no xterm. Sequences follow xterm's own encoding (CSI 1;<mod> for modified cursor\n * keys, SS3 in application cursor mode), so full-screen programs (vim, less, htop) read them\n * exactly as they read a desktop keyboard.\n */\n\nexport type AccessoryKeyId =\n | \"esc\"\n | \"tab\"\n | \"ctrl\"\n | \"alt\"\n | \"left\"\n | \"down\"\n | \"up\"\n | \"right\"\n | \"pipe\"\n | \"slash\"\n | \"tilde\"\n | \"dash\"\n | \"home\"\n | \"end\"\n | \"pgup\"\n | \"pgdn\"\n | \"paste\"\n | \"hide\";\n\nexport interface AccessoryKeyDef {\n id: AccessoryKeyId;\n /** What the key shows. */\n label: string;\n /** What a screen reader says. */\n aria: string;\n /** send = bytes to the PTY; modifier = Ctrl/Alt state; action = paste / hide keyboard. */\n kind: \"send\" | \"modifier\" | \"action\";\n /** Keys sharing a group sit together; a divider separates groups. */\n group: number;\n}\n\n/** Termius order: Esc Tab Ctrl Alt | ← ↓ ↑ → | | / ~ - | Home End PgUp PgDn | Paste, hide. */\nexport const ACCESSORY_KEYS: readonly AccessoryKeyDef[] = [\n { id: \"esc\", label: \"Esc\", aria: \"Escape\", kind: \"send\", group: 0 },\n { id: \"tab\", label: \"Tab\", aria: \"Tab\", kind: \"send\", group: 0 },\n { id: \"ctrl\", label: \"Ctrl\", aria: \"Control\", kind: \"modifier\", group: 0 },\n { id: \"alt\", label: \"Alt\", aria: \"Alt\", kind: \"modifier\", group: 0 },\n { id: \"left\", label: \"←\", aria: \"Left arrow\", kind: \"send\", group: 1 },\n { id: \"down\", label: \"↓\", aria: \"Down arrow\", kind: \"send\", group: 1 },\n { id: \"up\", label: \"↑\", aria: \"Up arrow\", kind: \"send\", group: 1 },\n { id: \"right\", label: \"→\", aria: \"Right arrow\", kind: \"send\", group: 1 },\n { id: \"pipe\", label: \"|\", aria: \"Pipe\", kind: \"send\", group: 2 },\n { id: \"slash\", label: \"/\", aria: \"Slash\", kind: \"send\", group: 2 },\n { id: \"tilde\", label: \"~\", aria: \"Tilde\", kind: \"send\", group: 2 },\n { id: \"dash\", label: \"-\", aria: \"Dash\", kind: \"send\", group: 2 },\n { id: \"home\", label: \"Home\", aria: \"Home\", kind: \"send\", group: 3 },\n { id: \"end\", label: \"End\", aria: \"End\", kind: \"send\", group: 3 },\n { id: \"pgup\", label: \"PgUp\", aria: \"Page up\", kind: \"send\", group: 3 },\n { id: \"pgdn\", label: \"PgDn\", aria: \"Page down\", kind: \"send\", group: 3 },\n { id: \"paste\", label: \"Paste\", aria: \"Paste\", kind: \"action\", group: 4 },\n { id: \"hide\", label: \"Hide\", aria: \"Hide keyboard\", kind: \"action\", group: 4 },\n];\n\nexport interface ActiveModifiers {\n ctrl: boolean;\n alt: boolean;\n}\n\nexport interface KeyModes {\n /** DECCKM — xterm's `modes.applicationCursorKeysMode`. */\n applicationCursor: boolean;\n}\n\nconst ESC = \"\\x1b\";\nconst CURSOR: Partial<Record<AccessoryKeyId, string>> = { up: \"A\", down: \"B\", right: \"C\", left: \"D\", home: \"H\", end: \"F\" };\nconst TILDE: Partial<Record<AccessoryKeyId, string>> = { pgup: \"5\", pgdn: \"6\" };\nconst LITERAL: Partial<Record<AccessoryKeyId, string>> = { pipe: \"|\", slash: \"/\", tilde: \"~\", dash: \"-\", tab: \"\\t\", esc: ESC };\n\n/** xterm's modifier parameter: 1 + shift(1) + alt(2) + ctrl(4). */\nfunction modParam(mods: ActiveModifiers): number {\n return 1 + (mods.alt ? 2 : 0) + (mods.ctrl ? 4 : 0);\n}\n\n/**\n * Bytes for a \"send\" key with the active modifiers applied, or null for a modifier/action key.\n * Modifiers on literal keys go through `applyModifiers` (Ctrl+/ → 0x1f, Alt+| → ESC |).\n */\nexport function keySequence(id: AccessoryKeyId, mods: ActiveModifiers, modes: KeyModes): string | null {\n const cursor = CURSOR[id];\n if (cursor !== undefined) {\n const m = modParam(mods);\n if (m > 1) return `${ESC}[1;${m}${cursor}`;\n return modes.applicationCursor ? `${ESC}O${cursor}` : `${ESC}[${cursor}`;\n }\n const tilde = TILDE[id];\n if (tilde !== undefined) {\n const m = modParam(mods);\n return m > 1 ? `${ESC}[${tilde};${m}~` : `${ESC}[${tilde}~`;\n }\n const literal = LITERAL[id];\n if (literal !== undefined) {\n if (id === \"tab\" && mods.ctrl === false && mods.alt === false) return literal;\n if (id === \"esc\") return mods.alt ? ESC + ESC : ESC;\n return applyModifiers(literal, mods).data;\n }\n return null;\n}\n\n/** Ctrl applied to one character, as a terminal keyboard encodes it; null when Ctrl has no code. */\nexport function controlCode(char: string): string | null {\n if (char.length !== 1) return null;\n const code = char.charCodeAt(0);\n if (code >= 0x61 && code <= 0x7a) return String.fromCharCode(code - 0x60); // a-z\n if (code >= 0x40 && code <= 0x5f) return String.fromCharCode(code - 0x40); // @ A-Z [ \\ ] ^ _\n switch (char) {\n case \" \":\n case \"2\":\n return \"\\x00\";\n case \"3\":\n return \"\\x1b\";\n case \"4\":\n return \"\\x1c\";\n case \"5\":\n return \"\\x1d\";\n case \"6\":\n return \"\\x1e\";\n case \"7\":\n case \"/\":\n case \"-\":\n return \"\\x1f\";\n case \"8\":\n case \"?\":\n return \"\\x7f\";\n default:\n return null;\n }\n}\n\n/**\n * Apply one-shot or locked modifiers to what the keyboard typed. Only a single key press is\n * modified — a paste or a multi-character IME commit passes through and does NOT consume the\n * modifier, so \"Ctrl, then paste\" never mangles the paste.\n */\nexport function applyModifiers(data: string, mods: ActiveModifiers): { data: string; consumed: boolean } {\n if (!mods.ctrl && !mods.alt) return { data, consumed: false };\n const chars = Array.from(data);\n if (chars.length !== 1) {\n // A cursor key typed on a hardware keyboard while Ctrl/Alt are latched: re-encode it.\n const m = /^\\x1b(?:\\[|O)([ABCDHF])$/.exec(data);\n if (m) return { data: `${ESC}[1;${modParam(mods)}${m[1]}`, consumed: true };\n return { data, consumed: false };\n }\n let out = data;\n if (mods.ctrl) {\n const ctl = controlCode(data);\n if (ctl !== null) out = ctl;\n }\n if (mods.alt) out = ESC + out;\n return { data: out, consumed: true };\n}\n","/**\n * Ctrl / Alt on the accessory bar: tap once = the next key only; tap twice quickly = locked until\n * tapped again. Pure state machine; the clock is passed in.\n */\n\nexport type ModifierState = \"off\" | \"once\" | \"locked\";\n\nexport interface ModifierLatch {\n state: ModifierState;\n /** When the latch was last tapped (ms), for double-tap detection. */\n tappedAt: number | null;\n}\n\n/** Two taps within this window lock the modifier (iOS's own double-tap timing for Shift lock). */\nexport const DOUBLE_TAP_MS = 350;\n\nexport const MODIFIER_OFF: ModifierLatch = { state: \"off\", tappedAt: null };\n\nexport function tapModifier(latch: ModifierLatch, now: number): ModifierLatch {\n switch (latch.state) {\n case \"off\":\n return { state: \"once\", tappedAt: now };\n case \"once\":\n return latch.tappedAt !== null && now - latch.tappedAt <= DOUBLE_TAP_MS\n ? { state: \"locked\", tappedAt: now }\n : { state: \"off\", tappedAt: now };\n case \"locked\":\n return { state: \"off\", tappedAt: now };\n }\n}\n\n/** After a key used the modifier: a one-shot latch releases, a locked one holds. */\nexport function afterKey(latch: ModifierLatch): ModifierLatch {\n return latch.state === \"once\" ? { state: \"off\", tappedAt: latch.tappedAt } : latch;\n}\n\nexport function isActive(latch: ModifierLatch): boolean {\n return latch.state !== \"off\";\n}\n","/**\n * One-finger gestures on a terminal, as iOS terminals (Termius, Blink) read them:\n *\n * tap → focus (opens the keyboard)\n * drag up / down → scroll the scrollback, by whole lines, with momentum on release\n * press and hold, no move → select the word under the finger; keep dragging to extend\n *\n * Pure: the host feeds pointer samples and the long-press timeout, and acts on what comes back.\n */\n\nexport interface GestureSample {\n x: number;\n y: number;\n /** ms */\n t: number;\n}\n\nexport type GestureAction =\n | { kind: \"tap\"; x: number; y: number }\n /** Positive = toward newer output (finger moved up). Whole lines only; the remainder is kept. */\n | { kind: \"scroll\"; lines: number }\n /** Finger lifted mid-scroll: continue with `flingStep` from this velocity (lines/ms, same sign as scroll). */\n | { kind: \"fling\"; velocity: number }\n | { kind: \"select-start\"; x: number; y: number }\n | { kind: \"select-extend\"; x: number; y: number }\n | { kind: \"select-end\" };\n\nexport interface GestureOptions {\n /** Row height in px (font size × line height). */\n lineHeight: number;\n /** Movement under this many px is still a tap / hold. */\n slopPx?: number;\n}\n\n/** Press-and-hold time before selection starts (iOS text selection is ~500 ms; terminals use less). */\nexport const LONG_PRESS_MS = 450;\n/** Momentum decay per 16 ms frame. */\nexport const FLING_FRICTION = 0.95;\n/** Below this (lines/ms) momentum stops. */\nexport const FLING_STOP = 0.002;\n/** Releases slower than this (lines/ms) do not fling. */\nconst FLING_MIN = 0.01;\n\ntype Phase = \"idle\" | \"pressed\" | \"scrolling\" | \"selecting\";\n\nexport interface GestureRecognizer {\n down(s: GestureSample): GestureAction[];\n move(s: GestureSample): GestureAction[];\n up(s: GestureSample): GestureAction[];\n cancel(): GestureAction[];\n /** The host's long-press timer fired (LONG_PRESS_MS after down). */\n longPress(): GestureAction[];\n readonly phase: Phase;\n}\n\nexport function createGestureRecognizer(options: GestureOptions): GestureRecognizer {\n const slop = options.slopPx ?? 8;\n const lineHeight = Math.max(1, options.lineHeight);\n let phase: Phase = \"idle\";\n let start: GestureSample = { x: 0, y: 0, t: 0 };\n let last: GestureSample = start;\n let carryPx = 0;\n /** Recent samples for release velocity. */\n let trail: GestureSample[] = [];\n\n function scrollBy(fromY: number, toY: number): GestureAction[] {\n carryPx += fromY - toY;\n const lines = carryPx > 0 ? Math.floor(carryPx / lineHeight) : Math.ceil(carryPx / lineHeight);\n if (lines === 0) return [];\n carryPx -= lines * lineHeight;\n return [{ kind: \"scroll\", lines }];\n }\n\n return {\n get phase() {\n return phase;\n },\n down(s) {\n phase = \"pressed\";\n start = s;\n last = s;\n carryPx = 0;\n trail = [s];\n return [];\n },\n move(s) {\n if (phase === \"idle\") return [];\n const out: GestureAction[] = [];\n if (phase === \"pressed\") {\n if (Math.hypot(s.x - start.x, s.y - start.y) <= slop) return [];\n phase = \"scrolling\";\n }\n if (phase === \"scrolling\") {\n out.push(...scrollBy(last.y, s.y));\n trail.push(s);\n while (trail.length > 2 && s.t - trail[0]!.t > 100) trail.shift();\n } else if (phase === \"selecting\") {\n out.push({ kind: \"select-extend\", x: s.x, y: s.y });\n }\n last = s;\n return out;\n },\n up(s) {\n const was = phase;\n phase = \"idle\";\n if (was === \"pressed\") return [{ kind: \"tap\", x: start.x, y: start.y }];\n if (was === \"selecting\") return [{ kind: \"select-end\" }];\n if (was === \"scrolling\") {\n const out = scrollBy(last.y, s.y);\n trail.push(s);\n const first = trail[0]!;\n const dt = s.t - first.t;\n const velocity = dt > 0 ? (first.y - s.y) / dt / lineHeight : 0;\n if (Math.abs(velocity) >= FLING_MIN) out.push({ kind: \"fling\", velocity });\n return out;\n }\n return [];\n },\n cancel() {\n const was = phase;\n phase = \"idle\";\n return was === \"selecting\" ? [{ kind: \"select-end\" }] : [];\n },\n longPress() {\n if (phase !== \"pressed\") return [];\n phase = \"selecting\";\n return [{ kind: \"select-start\", x: start.x, y: start.y }];\n },\n };\n}\n\n/**\n * One momentum frame: how far to move (lines, fractional) and the decayed velocity. The host\n * accumulates the fractional lines and scrolls whole ones until `velocity` is 0.\n */\nexport function flingStep(velocity: number, dtMs: number): { lines: number; velocity: number } {\n const lines = velocity * dtMs;\n const next = velocity * Math.pow(FLING_FRICTION, dtMs / 16);\n return { lines, velocity: Math.abs(next) < FLING_STOP ? 0 : next };\n}\n","/**\n * Long-press selection math in buffer cells. Pure; the React layer maps touch points to cells.\n */\n\nexport interface Cell {\n col: number;\n /** Absolute buffer row (scrollback included). */\n row: number;\n}\n\n/** Characters that belong to a \"word\" for a long press: paths, URLs, flags and hosts select whole. */\nconst WORD = /[A-Za-z0-9_\\-./~:@%+=,?&#]/;\n\n/** [start, end) of the word at `col` in `line`; a one-cell range on a separator. */\nexport function wordBounds(line: string, col: number): { start: number; end: number } {\n const chars = Array.from(line);\n if (col < 0 || col >= chars.length || !WORD.test(chars[col] ?? \" \")) return { start: col, end: col + 1 };\n let start = col;\n let end = col + 1;\n while (start > 0 && WORD.test(chars[start - 1]!)) start--;\n while (end < chars.length && WORD.test(chars[end]!)) end++;\n return { start, end };\n}\n\n/**\n * xterm's `select(column, row, length)` for the span between two cells (either order), where a\n * span wraps at `cols`. `anchor` is the first selected cell; `focus` is included too.\n */\nexport function spanBetween(anchor: Cell, focus: Cell, cols: number): { col: number; row: number; length: number } {\n const a = anchor.row * cols + anchor.col;\n const b = focus.row * cols + focus.col;\n const from = Math.min(a, b);\n const to = Math.max(a, b);\n return { col: from % cols, row: Math.floor(from / cols), length: to - from + 1 };\n}\n\n/** The cell under a point, given the screen element's box and the grid size; clamped to the grid. */\nexport function cellAt(\n point: { x: number; y: number },\n box: { left: number; top: number; width: number; height: number },\n grid: { cols: number; rows: number; viewportY: number },\n): Cell {\n const cw = box.width / Math.max(1, grid.cols);\n const ch = box.height / Math.max(1, grid.rows);\n const col = Math.min(grid.cols - 1, Math.max(0, Math.floor((point.x - box.left) / cw)));\n const screenRow = Math.min(grid.rows - 1, Math.max(0, Math.floor((point.y - box.top) / ch)));\n return { col, row: grid.viewportY + screenRow };\n}\n","/**\n * Visual-viewport arithmetic: where the on-screen keyboard is, and how tall a terminal anchored\n * at `top` may be so its last row and the accessory bar sit exactly above the keyboard. iOS\n * Safari shrinks only the VISUAL viewport when the keyboard opens; the layout viewport (and every\n * `100dvh`) stays full height, so a `bottom: 0` element ends up behind the keyboard.\n */\n\nexport interface ViewportBox {\n height: number;\n offsetTop: number;\n}\n\n/**\n * Below this the difference is browser chrome (Safari's collapsing toolbar), not a keyboard.\n * The smallest phone keyboard (iPhone SE landscape) is ~160px.\n */\nexport const KEYBOARD_MIN_PX = 120;\n\n/** Height the keyboard (or any bottom overlay) takes out of the layout viewport; 0 when closed. */\nexport function keyboardInset(layoutHeight: number, vv: ViewportBox | null): number {\n if (!vv) return 0;\n const inset = Math.round(layoutHeight - vv.height - vv.offsetTop);\n return inset >= KEYBOARD_MIN_PX ? inset : 0;\n}\n\n/** Height from `top` (layout px) down to the bottom of what is visible. Never negative. */\nexport function visibleHeightBelow(top: number, vv: ViewportBox | null, layoutHeight: number): number {\n const bottom = vv ? vv.offsetTop + vv.height : layoutHeight;\n return Math.max(0, Math.floor(bottom - top));\n}\n","/**\n * Terminal look: font, metrics and the two palettes. One place, so every terminal in every app\n * renders the same.\n */\nimport type { ITheme } from \"@xterm/xterm\";\n\n/** The system monospace first: SF Mono on Apple platforms, then the usual fallbacks. */\nexport const TERMINAL_FONT_FAMILY = 'ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace';\n\nexport const TERMINAL_DEFAULTS = {\n fontSize: 13,\n lineHeight: 1.2,\n scrollback: 5000,\n /** Side padding inside the terminal box, px. */\n padding: 8,\n} as const;\n\n/** The keyboard accessory bar's height, px (styles.css .mxt-bar: 44px keys + 4px above and below). */\nexport const ACCESSORY_BAR_HEIGHT = 52;\n\n/** The range a person can zoom the terminal text to (pinch, ⌘+ / ⌘−). */\nexport const FONT_SIZE_MIN = 9;\nexport const FONT_SIZE_MAX = 24;\n\n/** ⌘+ / ⌘− steps one point; the result stays in range. */\nexport function stepFontSize(current: number, delta: number): number {\n return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round(current) + delta));\n}\n\n/** A pinch scales the size it started from by the change in finger distance, to whole points. */\nexport function pinchFontSize(startSize: number, startDistance: number, distance: number): number {\n if (startDistance <= 0) return startSize;\n return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round((startSize * distance) / startDistance)));\n}\n\nexport function terminalTheme(dark: boolean): ITheme {\n return dark\n ? {\n background: \"#1e1e1e\",\n foreground: \"#e4e4e7\",\n cursor: \"#e4e4e7\",\n cursorAccent: \"#1e1e1e\",\n black: \"#1e1e1e\",\n red: \"#f87171\",\n green: \"#4ade80\",\n yellow: \"#facc15\",\n blue: \"#60a5fa\",\n magenta: \"#c084fc\",\n cyan: \"#22d3ee\",\n white: \"#e5e7eb\",\n brightBlack: \"#71717a\",\n brightRed: \"#fca5a5\",\n brightGreen: \"#86efac\",\n brightYellow: \"#fde047\",\n brightBlue: \"#93c5fd\",\n brightMagenta: \"#d8b4fe\",\n brightCyan: \"#67e8f9\",\n brightWhite: \"#f4f4f5\",\n selectionBackground: \"#3b82f680\",\n }\n : {\n background: \"#ffffff\",\n foreground: \"#18181b\",\n cursor: \"#18181b\",\n cursorAccent: \"#ffffff\",\n black: \"#18181b\",\n red: \"#b91c1c\",\n green: \"#15803d\",\n yellow: \"#b45309\",\n blue: \"#1d4ed8\",\n magenta: \"#7c3aed\",\n cyan: \"#0e7490\",\n white: \"#d4d4d8\",\n brightBlack: \"#71717a\",\n brightRed: \"#dc2626\",\n brightGreen: \"#16a34a\",\n brightYellow: \"#d97706\",\n brightBlue: \"#2563eb\",\n brightMagenta: \"#9333ea\",\n brightCyan: \"#0891b2\",\n brightWhite: \"#a1a1aa\",\n selectionBackground: \"#93c5fd99\",\n };\n}\n","/**\n * A shell-style line editor for a terminal whose other end is NOT a PTY (a one-shot exec API, a\n * read-only backend): prompt, cursor editing, history recall, Ctrl shortcuts, multi-line paste\n * that runs line by line. The terminal shows exactly what a shell would; the host only runs the\n * submitted line and calls `done()` when its output is written.\n *\n * Pure: it writes through `host.write` and never touches the DOM or xterm.\n */\n\nexport interface LineEditorHost {\n /** Bytes to the terminal (prompt, echo, cursor moves). */\n write(data: string): void;\n /** The prompt to draw before the line (may carry ANSI colour). Read on every redraw. */\n prompt(): string;\n /** Previous commands, oldest first. Read on every recall. */\n history(): readonly string[];\n /** Enter on a non-empty line. The editor is `busy` until the host calls `done()`. */\n onSubmit(line: string): void;\n /** Ctrl-C while busy (stop the running command). */\n onInterrupt?(): void;\n /** Ctrl-L: clear the screen (the editor redraws the prompt afterwards). */\n onClearScreen?(): void;\n /** The PERSON changed the line (typing, deleting, recalling history, Ctrl-C). Never for setLine. */\n onChange?(line: string): void;\n}\n\nexport interface LineEditor {\n /** Keystrokes / pastes from the terminal. */\n input(data: string): void;\n /** Draw the prompt and the current line on a fresh row. */\n prompt(): void;\n /** Replace the line from outside (an agent staging a command) and redraw it. Not a user change. */\n setLine(text: string): void;\n /** Redraw the current row (after the prompt itself changed, e.g. a new cwd). */\n refresh(): void;\n /** A submitted command finished: prompt again, then run the next pasted line, if any. */\n done(): void;\n readonly line: string;\n readonly busy: boolean;\n}\n\nconst CSI = \"\\x1b[\";\n\nexport function createLineEditor(host: LineEditorHost): LineEditor {\n let line = \"\";\n let cursor = 0;\n /** Index into history while recalling; null = editing a new line. */\n let recall: number | null = null;\n let busy = false;\n /** Lines of a multi-line paste still waiting to run. */\n const queue: string[] = [];\n /** What the person typed while a command ran (a shell keeps typeahead too); replayed after. */\n let typeahead = \"\";\n\n const changed = () => host.onChange?.(line);\n\n function redraw(): void {\n host.write(`\\r${CSI}K${host.prompt()}${line}`);\n const back = line.length - cursor;\n if (back > 0) host.write(`${CSI}${back}D`);\n }\n\n function promptRow(): void {\n host.write(`${host.prompt()}${line}`);\n const back = line.length - cursor;\n if (back > 0) host.write(`${CSI}${back}D`);\n }\n\n function submit(): void {\n const text = line;\n line = \"\";\n cursor = 0;\n recall = null;\n host.write(\"\\r\\n\");\n if (!text.trim()) {\n promptRow();\n next();\n return;\n }\n busy = true;\n host.onSubmit(text);\n }\n\n /** Run the next queued paste line, if any. */\n function next(): void {\n const queued = queue.shift();\n if (queued === undefined) return;\n line = queued;\n cursor = line.length;\n host.write(line);\n submit();\n }\n\n function insert(text: string): void {\n line = line.slice(0, cursor) + text + line.slice(cursor);\n cursor += text.length;\n if (cursor === line.length) host.write(text);\n else redraw();\n }\n\n function setFromHistory(index: number | null): void {\n const history = host.history();\n recall = index;\n line = index === null ? \"\" : (history[index] ?? \"\");\n cursor = line.length;\n redraw();\n changed();\n }\n\n function paste(data: string): void {\n const parts = data.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const first = parts.shift() ?? \"\";\n if (first) insert(first);\n if (parts.length === 0) {\n changed();\n return;\n }\n // A newline inside the paste: run what is on the line now, queue the rest one per command.\n queue.push(...parts.filter((p, i) => p !== \"\" || i < parts.length - 1));\n changed();\n submit();\n }\n\n function input(data: string): void {\n if (busy) {\n if (data === \"\\x03\") {\n typeahead = \"\";\n host.onInterrupt?.();\n } else {\n // Text and Enter only: a cursor key typed blind has nothing sensible to act on later.\n typeahead += data.replace(/\\x1b(?:\\[[0-9;]*[A-Za-z~]|O[A-Za-z])/g, \"\").replace(/[^\\r\\n\\t\\x20-\\x7e\\u00a0-\\uffff]/g, \"\");\n }\n return;\n }\n // Bracketed paste markers (xterm's paste in bracketed mode) are not ours to echo.\n const clean = data.replace(/\\x1b\\[20[01]~/g, \"\");\n if (clean.length > 1 && !clean.startsWith(\"\\x1b\")) {\n paste(clean);\n return;\n }\n switch (clean) {\n case \"\\r\":\n case \"\\n\":\n submit();\n return;\n case \"\\x7f\":\n case \"\\b\":\n if (cursor === 0) return;\n line = line.slice(0, cursor - 1) + line.slice(cursor);\n cursor -= 1;\n redraw();\n changed();\n return;\n case `${CSI}3~`:\n if (cursor >= line.length) return;\n line = line.slice(0, cursor) + line.slice(cursor + 1);\n redraw();\n changed();\n return;\n case `${CSI}A`:\n case \"\\x1bOA\": {\n const history = host.history();\n if (history.length === 0) return;\n setFromHistory(recall === null ? history.length - 1 : Math.max(0, recall - 1));\n return;\n }\n case `${CSI}B`:\n case \"\\x1bOB\": {\n if (recall === null) return;\n const nextIndex = recall + 1;\n setFromHistory(nextIndex >= host.history().length ? null : nextIndex);\n return;\n }\n case `${CSI}C`:\n case \"\\x1bOC\":\n if (cursor < line.length) {\n cursor += 1;\n host.write(`${CSI}C`);\n }\n return;\n case `${CSI}D`:\n case \"\\x1bOD\":\n if (cursor > 0) {\n cursor -= 1;\n host.write(`${CSI}D`);\n }\n return;\n case \"\\x01\":\n case `${CSI}H`:\n case \"\\x1bOH\":\n if (cursor > 0) {\n host.write(`${CSI}${cursor}D`);\n cursor = 0;\n }\n return;\n case \"\\x05\":\n case `${CSI}F`:\n case \"\\x1bOF\": {\n const distance = line.length - cursor;\n if (distance > 0) {\n host.write(`${CSI}${distance}C`);\n cursor = line.length;\n }\n return;\n }\n case \"\\x0b\":\n line = line.slice(0, cursor);\n redraw();\n changed();\n return;\n case \"\\x15\":\n line = line.slice(cursor);\n cursor = 0;\n redraw();\n changed();\n return;\n case \"\\x0c\":\n host.onClearScreen?.();\n redraw();\n return;\n case \"\\x03\":\n host.write(\"^C\\r\\n\");\n line = \"\";\n cursor = 0;\n recall = null;\n queue.length = 0;\n promptRow();\n changed();\n return;\n default:\n if (clean.length === 1 && clean >= \" \") {\n insert(clean);\n changed();\n }\n }\n }\n\n return {\n input,\n prompt: promptRow,\n refresh: redraw,\n setLine(text) {\n line = text;\n cursor = text.length;\n recall = null;\n if (!busy) redraw();\n },\n done() {\n busy = false;\n promptRow();\n if (queue.length > 0) {\n next();\n return;\n }\n if (typeahead) {\n const pending = typeahead;\n typeahead = \"\";\n if (pending.length === 1) input(pending);\n else paste(pending);\n }\n },\n get line() {\n return line;\n },\n get busy() {\n return busy;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { ITheme } from '@xterm/xterm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The keyboard accessory bar's keys and the bytes each sends — the keys a phone keyboard lacks.
|
|
5
|
+
* Pure: no DOM, no xterm. Sequences follow xterm's own encoding (CSI 1;<mod> for modified cursor
|
|
6
|
+
* keys, SS3 in application cursor mode), so full-screen programs (vim, less, htop) read them
|
|
7
|
+
* exactly as they read a desktop keyboard.
|
|
8
|
+
*/
|
|
9
|
+
type AccessoryKeyId = "esc" | "tab" | "ctrl" | "alt" | "left" | "down" | "up" | "right" | "pipe" | "slash" | "tilde" | "dash" | "home" | "end" | "pgup" | "pgdn" | "paste" | "hide";
|
|
10
|
+
interface AccessoryKeyDef {
|
|
11
|
+
id: AccessoryKeyId;
|
|
12
|
+
/** What the key shows. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** What a screen reader says. */
|
|
15
|
+
aria: string;
|
|
16
|
+
/** send = bytes to the PTY; modifier = Ctrl/Alt state; action = paste / hide keyboard. */
|
|
17
|
+
kind: "send" | "modifier" | "action";
|
|
18
|
+
/** Keys sharing a group sit together; a divider separates groups. */
|
|
19
|
+
group: number;
|
|
20
|
+
}
|
|
21
|
+
/** Termius order: Esc Tab Ctrl Alt | ← ↓ ↑ → | | / ~ - | Home End PgUp PgDn | Paste, hide. */
|
|
22
|
+
declare const ACCESSORY_KEYS: readonly AccessoryKeyDef[];
|
|
23
|
+
interface ActiveModifiers {
|
|
24
|
+
ctrl: boolean;
|
|
25
|
+
alt: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface KeyModes {
|
|
28
|
+
/** DECCKM — xterm's `modes.applicationCursorKeysMode`. */
|
|
29
|
+
applicationCursor: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bytes for a "send" key with the active modifiers applied, or null for a modifier/action key.
|
|
33
|
+
* Modifiers on literal keys go through `applyModifiers` (Ctrl+/ → 0x1f, Alt+| → ESC |).
|
|
34
|
+
*/
|
|
35
|
+
declare function keySequence(id: AccessoryKeyId, mods: ActiveModifiers, modes: KeyModes): string | null;
|
|
36
|
+
/** Ctrl applied to one character, as a terminal keyboard encodes it; null when Ctrl has no code. */
|
|
37
|
+
declare function controlCode(char: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Apply one-shot or locked modifiers to what the keyboard typed. Only a single key press is
|
|
40
|
+
* modified — a paste or a multi-character IME commit passes through and does NOT consume the
|
|
41
|
+
* modifier, so "Ctrl, then paste" never mangles the paste.
|
|
42
|
+
*/
|
|
43
|
+
declare function applyModifiers(data: string, mods: ActiveModifiers): {
|
|
44
|
+
data: string;
|
|
45
|
+
consumed: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Ctrl / Alt on the accessory bar: tap once = the next key only; tap twice quickly = locked until
|
|
50
|
+
* tapped again. Pure state machine; the clock is passed in.
|
|
51
|
+
*/
|
|
52
|
+
type ModifierState = "off" | "once" | "locked";
|
|
53
|
+
interface ModifierLatch {
|
|
54
|
+
state: ModifierState;
|
|
55
|
+
/** When the latch was last tapped (ms), for double-tap detection. */
|
|
56
|
+
tappedAt: number | null;
|
|
57
|
+
}
|
|
58
|
+
/** Two taps within this window lock the modifier (iOS's own double-tap timing for Shift lock). */
|
|
59
|
+
declare const DOUBLE_TAP_MS = 350;
|
|
60
|
+
declare const MODIFIER_OFF: ModifierLatch;
|
|
61
|
+
declare function tapModifier(latch: ModifierLatch, now: number): ModifierLatch;
|
|
62
|
+
/** After a key used the modifier: a one-shot latch releases, a locked one holds. */
|
|
63
|
+
declare function afterKey(latch: ModifierLatch): ModifierLatch;
|
|
64
|
+
declare function isActive(latch: ModifierLatch): boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One-finger gestures on a terminal, as iOS terminals (Termius, Blink) read them:
|
|
68
|
+
*
|
|
69
|
+
* tap → focus (opens the keyboard)
|
|
70
|
+
* drag up / down → scroll the scrollback, by whole lines, with momentum on release
|
|
71
|
+
* press and hold, no move → select the word under the finger; keep dragging to extend
|
|
72
|
+
*
|
|
73
|
+
* Pure: the host feeds pointer samples and the long-press timeout, and acts on what comes back.
|
|
74
|
+
*/
|
|
75
|
+
interface GestureSample {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
/** ms */
|
|
79
|
+
t: number;
|
|
80
|
+
}
|
|
81
|
+
type GestureAction = {
|
|
82
|
+
kind: "tap";
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}
|
|
86
|
+
/** Positive = toward newer output (finger moved up). Whole lines only; the remainder is kept. */
|
|
87
|
+
| {
|
|
88
|
+
kind: "scroll";
|
|
89
|
+
lines: number;
|
|
90
|
+
}
|
|
91
|
+
/** Finger lifted mid-scroll: continue with `flingStep` from this velocity (lines/ms, same sign as scroll). */
|
|
92
|
+
| {
|
|
93
|
+
kind: "fling";
|
|
94
|
+
velocity: number;
|
|
95
|
+
} | {
|
|
96
|
+
kind: "select-start";
|
|
97
|
+
x: number;
|
|
98
|
+
y: number;
|
|
99
|
+
} | {
|
|
100
|
+
kind: "select-extend";
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
} | {
|
|
104
|
+
kind: "select-end";
|
|
105
|
+
};
|
|
106
|
+
interface GestureOptions {
|
|
107
|
+
/** Row height in px (font size × line height). */
|
|
108
|
+
lineHeight: number;
|
|
109
|
+
/** Movement under this many px is still a tap / hold. */
|
|
110
|
+
slopPx?: number;
|
|
111
|
+
}
|
|
112
|
+
/** Press-and-hold time before selection starts (iOS text selection is ~500 ms; terminals use less). */
|
|
113
|
+
declare const LONG_PRESS_MS = 450;
|
|
114
|
+
/** Momentum decay per 16 ms frame. */
|
|
115
|
+
declare const FLING_FRICTION = 0.95;
|
|
116
|
+
/** Below this (lines/ms) momentum stops. */
|
|
117
|
+
declare const FLING_STOP = 0.002;
|
|
118
|
+
type Phase = "idle" | "pressed" | "scrolling" | "selecting";
|
|
119
|
+
interface GestureRecognizer {
|
|
120
|
+
down(s: GestureSample): GestureAction[];
|
|
121
|
+
move(s: GestureSample): GestureAction[];
|
|
122
|
+
up(s: GestureSample): GestureAction[];
|
|
123
|
+
cancel(): GestureAction[];
|
|
124
|
+
/** The host's long-press timer fired (LONG_PRESS_MS after down). */
|
|
125
|
+
longPress(): GestureAction[];
|
|
126
|
+
readonly phase: Phase;
|
|
127
|
+
}
|
|
128
|
+
declare function createGestureRecognizer(options: GestureOptions): GestureRecognizer;
|
|
129
|
+
/**
|
|
130
|
+
* One momentum frame: how far to move (lines, fractional) and the decayed velocity. The host
|
|
131
|
+
* accumulates the fractional lines and scrolls whole ones until `velocity` is 0.
|
|
132
|
+
*/
|
|
133
|
+
declare function flingStep(velocity: number, dtMs: number): {
|
|
134
|
+
lines: number;
|
|
135
|
+
velocity: number;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Long-press selection math in buffer cells. Pure; the React layer maps touch points to cells.
|
|
140
|
+
*/
|
|
141
|
+
interface Cell {
|
|
142
|
+
col: number;
|
|
143
|
+
/** Absolute buffer row (scrollback included). */
|
|
144
|
+
row: number;
|
|
145
|
+
}
|
|
146
|
+
/** [start, end) of the word at `col` in `line`; a one-cell range on a separator. */
|
|
147
|
+
declare function wordBounds(line: string, col: number): {
|
|
148
|
+
start: number;
|
|
149
|
+
end: number;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* xterm's `select(column, row, length)` for the span between two cells (either order), where a
|
|
153
|
+
* span wraps at `cols`. `anchor` is the first selected cell; `focus` is included too.
|
|
154
|
+
*/
|
|
155
|
+
declare function spanBetween(anchor: Cell, focus: Cell, cols: number): {
|
|
156
|
+
col: number;
|
|
157
|
+
row: number;
|
|
158
|
+
length: number;
|
|
159
|
+
};
|
|
160
|
+
/** The cell under a point, given the screen element's box and the grid size; clamped to the grid. */
|
|
161
|
+
declare function cellAt(point: {
|
|
162
|
+
x: number;
|
|
163
|
+
y: number;
|
|
164
|
+
}, box: {
|
|
165
|
+
left: number;
|
|
166
|
+
top: number;
|
|
167
|
+
width: number;
|
|
168
|
+
height: number;
|
|
169
|
+
}, grid: {
|
|
170
|
+
cols: number;
|
|
171
|
+
rows: number;
|
|
172
|
+
viewportY: number;
|
|
173
|
+
}): Cell;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Visual-viewport arithmetic: where the on-screen keyboard is, and how tall a terminal anchored
|
|
177
|
+
* at `top` may be so its last row and the accessory bar sit exactly above the keyboard. iOS
|
|
178
|
+
* Safari shrinks only the VISUAL viewport when the keyboard opens; the layout viewport (and every
|
|
179
|
+
* `100dvh`) stays full height, so a `bottom: 0` element ends up behind the keyboard.
|
|
180
|
+
*/
|
|
181
|
+
interface ViewportBox {
|
|
182
|
+
height: number;
|
|
183
|
+
offsetTop: number;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Below this the difference is browser chrome (Safari's collapsing toolbar), not a keyboard.
|
|
187
|
+
* The smallest phone keyboard (iPhone SE landscape) is ~160px.
|
|
188
|
+
*/
|
|
189
|
+
declare const KEYBOARD_MIN_PX = 120;
|
|
190
|
+
/** Height the keyboard (or any bottom overlay) takes out of the layout viewport; 0 when closed. */
|
|
191
|
+
declare function keyboardInset(layoutHeight: number, vv: ViewportBox | null): number;
|
|
192
|
+
/** Height from `top` (layout px) down to the bottom of what is visible. Never negative. */
|
|
193
|
+
declare function visibleHeightBelow(top: number, vv: ViewportBox | null, layoutHeight: number): number;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Terminal look: font, metrics and the two palettes. One place, so every terminal in every app
|
|
197
|
+
* renders the same.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/** The system monospace first: SF Mono on Apple platforms, then the usual fallbacks. */
|
|
201
|
+
declare const TERMINAL_FONT_FAMILY = "ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
202
|
+
declare const TERMINAL_DEFAULTS: {
|
|
203
|
+
readonly fontSize: 13;
|
|
204
|
+
readonly lineHeight: 1.2;
|
|
205
|
+
readonly scrollback: 5000;
|
|
206
|
+
/** Side padding inside the terminal box, px. */
|
|
207
|
+
readonly padding: 8;
|
|
208
|
+
};
|
|
209
|
+
/** The keyboard accessory bar's height, px (styles.css .mxt-bar: 44px keys + 4px above and below). */
|
|
210
|
+
declare const ACCESSORY_BAR_HEIGHT = 52;
|
|
211
|
+
/** The range a person can zoom the terminal text to (pinch, ⌘+ / ⌘−). */
|
|
212
|
+
declare const FONT_SIZE_MIN = 9;
|
|
213
|
+
declare const FONT_SIZE_MAX = 24;
|
|
214
|
+
/** ⌘+ / ⌘− steps one point; the result stays in range. */
|
|
215
|
+
declare function stepFontSize(current: number, delta: number): number;
|
|
216
|
+
/** A pinch scales the size it started from by the change in finger distance, to whole points. */
|
|
217
|
+
declare function pinchFontSize(startSize: number, startDistance: number, distance: number): number;
|
|
218
|
+
declare function terminalTheme(dark: boolean): ITheme;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* A shell-style line editor for a terminal whose other end is NOT a PTY (a one-shot exec API, a
|
|
222
|
+
* read-only backend): prompt, cursor editing, history recall, Ctrl shortcuts, multi-line paste
|
|
223
|
+
* that runs line by line. The terminal shows exactly what a shell would; the host only runs the
|
|
224
|
+
* submitted line and calls `done()` when its output is written.
|
|
225
|
+
*
|
|
226
|
+
* Pure: it writes through `host.write` and never touches the DOM or xterm.
|
|
227
|
+
*/
|
|
228
|
+
interface LineEditorHost {
|
|
229
|
+
/** Bytes to the terminal (prompt, echo, cursor moves). */
|
|
230
|
+
write(data: string): void;
|
|
231
|
+
/** The prompt to draw before the line (may carry ANSI colour). Read on every redraw. */
|
|
232
|
+
prompt(): string;
|
|
233
|
+
/** Previous commands, oldest first. Read on every recall. */
|
|
234
|
+
history(): readonly string[];
|
|
235
|
+
/** Enter on a non-empty line. The editor is `busy` until the host calls `done()`. */
|
|
236
|
+
onSubmit(line: string): void;
|
|
237
|
+
/** Ctrl-C while busy (stop the running command). */
|
|
238
|
+
onInterrupt?(): void;
|
|
239
|
+
/** Ctrl-L: clear the screen (the editor redraws the prompt afterwards). */
|
|
240
|
+
onClearScreen?(): void;
|
|
241
|
+
/** The PERSON changed the line (typing, deleting, recalling history, Ctrl-C). Never for setLine. */
|
|
242
|
+
onChange?(line: string): void;
|
|
243
|
+
}
|
|
244
|
+
interface LineEditor {
|
|
245
|
+
/** Keystrokes / pastes from the terminal. */
|
|
246
|
+
input(data: string): void;
|
|
247
|
+
/** Draw the prompt and the current line on a fresh row. */
|
|
248
|
+
prompt(): void;
|
|
249
|
+
/** Replace the line from outside (an agent staging a command) and redraw it. Not a user change. */
|
|
250
|
+
setLine(text: string): void;
|
|
251
|
+
/** Redraw the current row (after the prompt itself changed, e.g. a new cwd). */
|
|
252
|
+
refresh(): void;
|
|
253
|
+
/** A submitted command finished: prompt again, then run the next pasted line, if any. */
|
|
254
|
+
done(): void;
|
|
255
|
+
readonly line: string;
|
|
256
|
+
readonly busy: boolean;
|
|
257
|
+
}
|
|
258
|
+
declare function createLineEditor(host: LineEditorHost): LineEditor;
|
|
259
|
+
|
|
260
|
+
export { ACCESSORY_BAR_HEIGHT, ACCESSORY_KEYS, type AccessoryKeyDef, type AccessoryKeyId, type ActiveModifiers, type Cell, DOUBLE_TAP_MS, FLING_FRICTION, FLING_STOP, FONT_SIZE_MAX, FONT_SIZE_MIN, type GestureAction, type GestureOptions, type GestureRecognizer, type GestureSample, KEYBOARD_MIN_PX, type KeyModes, LONG_PRESS_MS, type LineEditor, type LineEditorHost, MODIFIER_OFF, type ModifierLatch, type ModifierState, TERMINAL_DEFAULTS, TERMINAL_FONT_FAMILY, type ViewportBox, afterKey, applyModifiers, cellAt, controlCode, createGestureRecognizer, createLineEditor, flingStep, isActive, keySequence, keyboardInset, pinchFontSize, spanBetween, stepFontSize, tapModifier, terminalTheme, visibleHeightBelow, wordBounds };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { ITheme } from '@xterm/xterm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The keyboard accessory bar's keys and the bytes each sends — the keys a phone keyboard lacks.
|
|
5
|
+
* Pure: no DOM, no xterm. Sequences follow xterm's own encoding (CSI 1;<mod> for modified cursor
|
|
6
|
+
* keys, SS3 in application cursor mode), so full-screen programs (vim, less, htop) read them
|
|
7
|
+
* exactly as they read a desktop keyboard.
|
|
8
|
+
*/
|
|
9
|
+
type AccessoryKeyId = "esc" | "tab" | "ctrl" | "alt" | "left" | "down" | "up" | "right" | "pipe" | "slash" | "tilde" | "dash" | "home" | "end" | "pgup" | "pgdn" | "paste" | "hide";
|
|
10
|
+
interface AccessoryKeyDef {
|
|
11
|
+
id: AccessoryKeyId;
|
|
12
|
+
/** What the key shows. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** What a screen reader says. */
|
|
15
|
+
aria: string;
|
|
16
|
+
/** send = bytes to the PTY; modifier = Ctrl/Alt state; action = paste / hide keyboard. */
|
|
17
|
+
kind: "send" | "modifier" | "action";
|
|
18
|
+
/** Keys sharing a group sit together; a divider separates groups. */
|
|
19
|
+
group: number;
|
|
20
|
+
}
|
|
21
|
+
/** Termius order: Esc Tab Ctrl Alt | ← ↓ ↑ → | | / ~ - | Home End PgUp PgDn | Paste, hide. */
|
|
22
|
+
declare const ACCESSORY_KEYS: readonly AccessoryKeyDef[];
|
|
23
|
+
interface ActiveModifiers {
|
|
24
|
+
ctrl: boolean;
|
|
25
|
+
alt: boolean;
|
|
26
|
+
}
|
|
27
|
+
interface KeyModes {
|
|
28
|
+
/** DECCKM — xterm's `modes.applicationCursorKeysMode`. */
|
|
29
|
+
applicationCursor: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bytes for a "send" key with the active modifiers applied, or null for a modifier/action key.
|
|
33
|
+
* Modifiers on literal keys go through `applyModifiers` (Ctrl+/ → 0x1f, Alt+| → ESC |).
|
|
34
|
+
*/
|
|
35
|
+
declare function keySequence(id: AccessoryKeyId, mods: ActiveModifiers, modes: KeyModes): string | null;
|
|
36
|
+
/** Ctrl applied to one character, as a terminal keyboard encodes it; null when Ctrl has no code. */
|
|
37
|
+
declare function controlCode(char: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Apply one-shot or locked modifiers to what the keyboard typed. Only a single key press is
|
|
40
|
+
* modified — a paste or a multi-character IME commit passes through and does NOT consume the
|
|
41
|
+
* modifier, so "Ctrl, then paste" never mangles the paste.
|
|
42
|
+
*/
|
|
43
|
+
declare function applyModifiers(data: string, mods: ActiveModifiers): {
|
|
44
|
+
data: string;
|
|
45
|
+
consumed: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Ctrl / Alt on the accessory bar: tap once = the next key only; tap twice quickly = locked until
|
|
50
|
+
* tapped again. Pure state machine; the clock is passed in.
|
|
51
|
+
*/
|
|
52
|
+
type ModifierState = "off" | "once" | "locked";
|
|
53
|
+
interface ModifierLatch {
|
|
54
|
+
state: ModifierState;
|
|
55
|
+
/** When the latch was last tapped (ms), for double-tap detection. */
|
|
56
|
+
tappedAt: number | null;
|
|
57
|
+
}
|
|
58
|
+
/** Two taps within this window lock the modifier (iOS's own double-tap timing for Shift lock). */
|
|
59
|
+
declare const DOUBLE_TAP_MS = 350;
|
|
60
|
+
declare const MODIFIER_OFF: ModifierLatch;
|
|
61
|
+
declare function tapModifier(latch: ModifierLatch, now: number): ModifierLatch;
|
|
62
|
+
/** After a key used the modifier: a one-shot latch releases, a locked one holds. */
|
|
63
|
+
declare function afterKey(latch: ModifierLatch): ModifierLatch;
|
|
64
|
+
declare function isActive(latch: ModifierLatch): boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* One-finger gestures on a terminal, as iOS terminals (Termius, Blink) read them:
|
|
68
|
+
*
|
|
69
|
+
* tap → focus (opens the keyboard)
|
|
70
|
+
* drag up / down → scroll the scrollback, by whole lines, with momentum on release
|
|
71
|
+
* press and hold, no move → select the word under the finger; keep dragging to extend
|
|
72
|
+
*
|
|
73
|
+
* Pure: the host feeds pointer samples and the long-press timeout, and acts on what comes back.
|
|
74
|
+
*/
|
|
75
|
+
interface GestureSample {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
/** ms */
|
|
79
|
+
t: number;
|
|
80
|
+
}
|
|
81
|
+
type GestureAction = {
|
|
82
|
+
kind: "tap";
|
|
83
|
+
x: number;
|
|
84
|
+
y: number;
|
|
85
|
+
}
|
|
86
|
+
/** Positive = toward newer output (finger moved up). Whole lines only; the remainder is kept. */
|
|
87
|
+
| {
|
|
88
|
+
kind: "scroll";
|
|
89
|
+
lines: number;
|
|
90
|
+
}
|
|
91
|
+
/** Finger lifted mid-scroll: continue with `flingStep` from this velocity (lines/ms, same sign as scroll). */
|
|
92
|
+
| {
|
|
93
|
+
kind: "fling";
|
|
94
|
+
velocity: number;
|
|
95
|
+
} | {
|
|
96
|
+
kind: "select-start";
|
|
97
|
+
x: number;
|
|
98
|
+
y: number;
|
|
99
|
+
} | {
|
|
100
|
+
kind: "select-extend";
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
} | {
|
|
104
|
+
kind: "select-end";
|
|
105
|
+
};
|
|
106
|
+
interface GestureOptions {
|
|
107
|
+
/** Row height in px (font size × line height). */
|
|
108
|
+
lineHeight: number;
|
|
109
|
+
/** Movement under this many px is still a tap / hold. */
|
|
110
|
+
slopPx?: number;
|
|
111
|
+
}
|
|
112
|
+
/** Press-and-hold time before selection starts (iOS text selection is ~500 ms; terminals use less). */
|
|
113
|
+
declare const LONG_PRESS_MS = 450;
|
|
114
|
+
/** Momentum decay per 16 ms frame. */
|
|
115
|
+
declare const FLING_FRICTION = 0.95;
|
|
116
|
+
/** Below this (lines/ms) momentum stops. */
|
|
117
|
+
declare const FLING_STOP = 0.002;
|
|
118
|
+
type Phase = "idle" | "pressed" | "scrolling" | "selecting";
|
|
119
|
+
interface GestureRecognizer {
|
|
120
|
+
down(s: GestureSample): GestureAction[];
|
|
121
|
+
move(s: GestureSample): GestureAction[];
|
|
122
|
+
up(s: GestureSample): GestureAction[];
|
|
123
|
+
cancel(): GestureAction[];
|
|
124
|
+
/** The host's long-press timer fired (LONG_PRESS_MS after down). */
|
|
125
|
+
longPress(): GestureAction[];
|
|
126
|
+
readonly phase: Phase;
|
|
127
|
+
}
|
|
128
|
+
declare function createGestureRecognizer(options: GestureOptions): GestureRecognizer;
|
|
129
|
+
/**
|
|
130
|
+
* One momentum frame: how far to move (lines, fractional) and the decayed velocity. The host
|
|
131
|
+
* accumulates the fractional lines and scrolls whole ones until `velocity` is 0.
|
|
132
|
+
*/
|
|
133
|
+
declare function flingStep(velocity: number, dtMs: number): {
|
|
134
|
+
lines: number;
|
|
135
|
+
velocity: number;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Long-press selection math in buffer cells. Pure; the React layer maps touch points to cells.
|
|
140
|
+
*/
|
|
141
|
+
interface Cell {
|
|
142
|
+
col: number;
|
|
143
|
+
/** Absolute buffer row (scrollback included). */
|
|
144
|
+
row: number;
|
|
145
|
+
}
|
|
146
|
+
/** [start, end) of the word at `col` in `line`; a one-cell range on a separator. */
|
|
147
|
+
declare function wordBounds(line: string, col: number): {
|
|
148
|
+
start: number;
|
|
149
|
+
end: number;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* xterm's `select(column, row, length)` for the span between two cells (either order), where a
|
|
153
|
+
* span wraps at `cols`. `anchor` is the first selected cell; `focus` is included too.
|
|
154
|
+
*/
|
|
155
|
+
declare function spanBetween(anchor: Cell, focus: Cell, cols: number): {
|
|
156
|
+
col: number;
|
|
157
|
+
row: number;
|
|
158
|
+
length: number;
|
|
159
|
+
};
|
|
160
|
+
/** The cell under a point, given the screen element's box and the grid size; clamped to the grid. */
|
|
161
|
+
declare function cellAt(point: {
|
|
162
|
+
x: number;
|
|
163
|
+
y: number;
|
|
164
|
+
}, box: {
|
|
165
|
+
left: number;
|
|
166
|
+
top: number;
|
|
167
|
+
width: number;
|
|
168
|
+
height: number;
|
|
169
|
+
}, grid: {
|
|
170
|
+
cols: number;
|
|
171
|
+
rows: number;
|
|
172
|
+
viewportY: number;
|
|
173
|
+
}): Cell;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Visual-viewport arithmetic: where the on-screen keyboard is, and how tall a terminal anchored
|
|
177
|
+
* at `top` may be so its last row and the accessory bar sit exactly above the keyboard. iOS
|
|
178
|
+
* Safari shrinks only the VISUAL viewport when the keyboard opens; the layout viewport (and every
|
|
179
|
+
* `100dvh`) stays full height, so a `bottom: 0` element ends up behind the keyboard.
|
|
180
|
+
*/
|
|
181
|
+
interface ViewportBox {
|
|
182
|
+
height: number;
|
|
183
|
+
offsetTop: number;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Below this the difference is browser chrome (Safari's collapsing toolbar), not a keyboard.
|
|
187
|
+
* The smallest phone keyboard (iPhone SE landscape) is ~160px.
|
|
188
|
+
*/
|
|
189
|
+
declare const KEYBOARD_MIN_PX = 120;
|
|
190
|
+
/** Height the keyboard (or any bottom overlay) takes out of the layout viewport; 0 when closed. */
|
|
191
|
+
declare function keyboardInset(layoutHeight: number, vv: ViewportBox | null): number;
|
|
192
|
+
/** Height from `top` (layout px) down to the bottom of what is visible. Never negative. */
|
|
193
|
+
declare function visibleHeightBelow(top: number, vv: ViewportBox | null, layoutHeight: number): number;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Terminal look: font, metrics and the two palettes. One place, so every terminal in every app
|
|
197
|
+
* renders the same.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/** The system monospace first: SF Mono on Apple platforms, then the usual fallbacks. */
|
|
201
|
+
declare const TERMINAL_FONT_FAMILY = "ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
202
|
+
declare const TERMINAL_DEFAULTS: {
|
|
203
|
+
readonly fontSize: 13;
|
|
204
|
+
readonly lineHeight: 1.2;
|
|
205
|
+
readonly scrollback: 5000;
|
|
206
|
+
/** Side padding inside the terminal box, px. */
|
|
207
|
+
readonly padding: 8;
|
|
208
|
+
};
|
|
209
|
+
/** The keyboard accessory bar's height, px (styles.css .mxt-bar: 44px keys + 4px above and below). */
|
|
210
|
+
declare const ACCESSORY_BAR_HEIGHT = 52;
|
|
211
|
+
/** The range a person can zoom the terminal text to (pinch, ⌘+ / ⌘−). */
|
|
212
|
+
declare const FONT_SIZE_MIN = 9;
|
|
213
|
+
declare const FONT_SIZE_MAX = 24;
|
|
214
|
+
/** ⌘+ / ⌘− steps one point; the result stays in range. */
|
|
215
|
+
declare function stepFontSize(current: number, delta: number): number;
|
|
216
|
+
/** A pinch scales the size it started from by the change in finger distance, to whole points. */
|
|
217
|
+
declare function pinchFontSize(startSize: number, startDistance: number, distance: number): number;
|
|
218
|
+
declare function terminalTheme(dark: boolean): ITheme;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* A shell-style line editor for a terminal whose other end is NOT a PTY (a one-shot exec API, a
|
|
222
|
+
* read-only backend): prompt, cursor editing, history recall, Ctrl shortcuts, multi-line paste
|
|
223
|
+
* that runs line by line. The terminal shows exactly what a shell would; the host only runs the
|
|
224
|
+
* submitted line and calls `done()` when its output is written.
|
|
225
|
+
*
|
|
226
|
+
* Pure: it writes through `host.write` and never touches the DOM or xterm.
|
|
227
|
+
*/
|
|
228
|
+
interface LineEditorHost {
|
|
229
|
+
/** Bytes to the terminal (prompt, echo, cursor moves). */
|
|
230
|
+
write(data: string): void;
|
|
231
|
+
/** The prompt to draw before the line (may carry ANSI colour). Read on every redraw. */
|
|
232
|
+
prompt(): string;
|
|
233
|
+
/** Previous commands, oldest first. Read on every recall. */
|
|
234
|
+
history(): readonly string[];
|
|
235
|
+
/** Enter on a non-empty line. The editor is `busy` until the host calls `done()`. */
|
|
236
|
+
onSubmit(line: string): void;
|
|
237
|
+
/** Ctrl-C while busy (stop the running command). */
|
|
238
|
+
onInterrupt?(): void;
|
|
239
|
+
/** Ctrl-L: clear the screen (the editor redraws the prompt afterwards). */
|
|
240
|
+
onClearScreen?(): void;
|
|
241
|
+
/** The PERSON changed the line (typing, deleting, recalling history, Ctrl-C). Never for setLine. */
|
|
242
|
+
onChange?(line: string): void;
|
|
243
|
+
}
|
|
244
|
+
interface LineEditor {
|
|
245
|
+
/** Keystrokes / pastes from the terminal. */
|
|
246
|
+
input(data: string): void;
|
|
247
|
+
/** Draw the prompt and the current line on a fresh row. */
|
|
248
|
+
prompt(): void;
|
|
249
|
+
/** Replace the line from outside (an agent staging a command) and redraw it. Not a user change. */
|
|
250
|
+
setLine(text: string): void;
|
|
251
|
+
/** Redraw the current row (after the prompt itself changed, e.g. a new cwd). */
|
|
252
|
+
refresh(): void;
|
|
253
|
+
/** A submitted command finished: prompt again, then run the next pasted line, if any. */
|
|
254
|
+
done(): void;
|
|
255
|
+
readonly line: string;
|
|
256
|
+
readonly busy: boolean;
|
|
257
|
+
}
|
|
258
|
+
declare function createLineEditor(host: LineEditorHost): LineEditor;
|
|
259
|
+
|
|
260
|
+
export { ACCESSORY_BAR_HEIGHT, ACCESSORY_KEYS, type AccessoryKeyDef, type AccessoryKeyId, type ActiveModifiers, type Cell, DOUBLE_TAP_MS, FLING_FRICTION, FLING_STOP, FONT_SIZE_MAX, FONT_SIZE_MIN, type GestureAction, type GestureOptions, type GestureRecognizer, type GestureSample, KEYBOARD_MIN_PX, type KeyModes, LONG_PRESS_MS, type LineEditor, type LineEditorHost, MODIFIER_OFF, type ModifierLatch, type ModifierState, TERMINAL_DEFAULTS, TERMINAL_FONT_FAMILY, type ViewportBox, afterKey, applyModifiers, cellAt, controlCode, createGestureRecognizer, createLineEditor, flingStep, isActive, keySequence, keyboardInset, pinchFontSize, spanBetween, stepFontSize, tapModifier, terminalTheme, visibleHeightBelow, wordBounds };
|