@cplieger/web-terminal-engine 1.2.0 → 1.3.0
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/jsr.json +1 -1
- package/package.json +3 -3
- package/src/keyboard.ts +15 -7
- package/src/render.ts +23 -5
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsgo -p tsconfig.json",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@eslint/js": "10.0.1",
|
|
12
12
|
"@vitest/coverage-v8": "4.1.9",
|
|
13
|
-
"eslint": "10.
|
|
13
|
+
"eslint": "10.6.0",
|
|
14
14
|
"fast-check": "4.8.0",
|
|
15
15
|
"happy-dom": "20.10.6",
|
|
16
|
-
"prettier": "3.8.
|
|
16
|
+
"prettier": "3.8.5",
|
|
17
17
|
"typescript": "6.0.3",
|
|
18
18
|
"typescript-eslint": "8.62.0",
|
|
19
19
|
"vitest": "4.1.9"
|
package/src/keyboard.ts
CHANGED
|
@@ -470,7 +470,7 @@ export interface MobileToolbarController {
|
|
|
470
470
|
* would garble it.
|
|
471
471
|
*/
|
|
472
472
|
applyStickyCtrl(text: string): string;
|
|
473
|
-
/** Programmatically arm/disarm Ctrl. Updates the toolbar button visuals + fires `onCtrlChange`. */
|
|
473
|
+
/** Programmatically arm/disarm Ctrl. Updates the toolbar button visuals (`.armed` class + `aria-pressed`) + fires `onCtrlChange`. */
|
|
474
474
|
setCtrlArmed(on: boolean): void;
|
|
475
475
|
/** Whether sticky-Ctrl is currently armed. */
|
|
476
476
|
isCtrlArmed(): boolean;
|
|
@@ -513,20 +513,28 @@ export function bindMobileToolbar(opts: BindMobileToolbarOptions): MobileToolbar
|
|
|
513
513
|
const enterBtn = findBtn(ids.enter);
|
|
514
514
|
const escBtn = findBtn(ids.esc);
|
|
515
515
|
|
|
516
|
+
function paintCtrlBtn(on: boolean): void {
|
|
517
|
+
if (!ctrlBtn) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
ctrlBtn.classList.toggle("armed", on);
|
|
521
|
+
// Keep the ARIA toggle state in sync with the visual `.armed` class so
|
|
522
|
+
// assistive tech announces the sticky-Ctrl button as pressed/unpressed.
|
|
523
|
+
// The scaffold ships kb-ctrl as `aria-pressed="false"`; consumers used to
|
|
524
|
+
// own this in their own setCtrlArmed before delegating the toolbar here.
|
|
525
|
+
ctrlBtn.setAttribute("aria-pressed", on ? "true" : "false");
|
|
526
|
+
}
|
|
527
|
+
|
|
516
528
|
function setCtrlArmed(on: boolean): void {
|
|
517
529
|
if (armed === on) {
|
|
518
530
|
// Still update visuals defensively (e.g. after dispose was called
|
|
519
531
|
// and someone re-armed via setCtrlArmed) — but skip the change
|
|
520
532
|
// notification to keep onCtrlChange edge-triggered.
|
|
521
|
-
|
|
522
|
-
ctrlBtn.classList.toggle("armed", on);
|
|
523
|
-
}
|
|
533
|
+
paintCtrlBtn(on);
|
|
524
534
|
return;
|
|
525
535
|
}
|
|
526
536
|
armed = on;
|
|
527
|
-
|
|
528
|
-
ctrlBtn.classList.toggle("armed", on);
|
|
529
|
-
}
|
|
537
|
+
paintCtrlBtn(on);
|
|
530
538
|
opts.onCtrlChange?.(on);
|
|
531
539
|
}
|
|
532
540
|
|
package/src/render.ts
CHANGED
|
@@ -178,6 +178,12 @@ export function init(opts: {
|
|
|
178
178
|
rowEls.clear();
|
|
179
179
|
renderQueue.clear();
|
|
180
180
|
trimMarkerEl = null;
|
|
181
|
+
// Drop the predicted-cursor overlay (re-created lazily against the new
|
|
182
|
+
// termWrap on the next setPredictedCursor call) so re-init starts clean.
|
|
183
|
+
if (predCursorEl) {
|
|
184
|
+
predCursorEl.remove();
|
|
185
|
+
predCursorEl = null;
|
|
186
|
+
}
|
|
181
187
|
output.replaceChildren();
|
|
182
188
|
cursorAbs = -1;
|
|
183
189
|
if (pendingFrame !== undefined) {
|
|
@@ -785,16 +791,28 @@ export function getCursorPx(): { left: number; top: number; cellH: number } {
|
|
|
785
791
|
|
|
786
792
|
let predCursorEl: HTMLElement | null = null;
|
|
787
793
|
|
|
794
|
+
// Create the predicted-cursor overlay the renderer owns. Appended to termWrap
|
|
795
|
+
// (the positioned scroll container) so its absolute left/top math matches the
|
|
796
|
+
// row offsets. Styled by the `.pred-cursor` class from the UI CSS bundle. The
|
|
797
|
+
// renderer owning this means the engine never depends on a host-provided
|
|
798
|
+
// `#pred-cursor` scaffold element.
|
|
799
|
+
function createPredCursorEl(): HTMLElement {
|
|
800
|
+
const el = document.createElement("div");
|
|
801
|
+
el.className = "pred-cursor";
|
|
802
|
+
el.setAttribute("aria-hidden", "true");
|
|
803
|
+
termWrap.appendChild(el);
|
|
804
|
+
return el;
|
|
805
|
+
}
|
|
806
|
+
|
|
788
807
|
/**
|
|
789
808
|
* Show or hide a "predicted" cursor overlay at window-relative (row, col).
|
|
790
809
|
* Useful for client-side echo of typed characters before the server
|
|
791
|
-
* acknowledges them, over high-latency connections.
|
|
810
|
+
* acknowledges them, over high-latency connections. The overlay element is
|
|
811
|
+
* created lazily on first use (a consumer that never predicts never creates
|
|
812
|
+
* it).
|
|
792
813
|
*/
|
|
793
814
|
export function setPredictedCursor(row: number, col: number, active: boolean): void {
|
|
794
|
-
const el = predCursorEl ?? (predCursorEl =
|
|
795
|
-
if (!el) {
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
815
|
+
const el = predCursorEl ?? (predCursorEl = createPredCursorEl());
|
|
798
816
|
const win = store.getWindow();
|
|
799
817
|
const predAbs = win.base + row;
|
|
800
818
|
if (!active || (predAbs === cursorAbs && col === cursorCol)) {
|