@cplieger/web-terminal-engine 3.0.6 → 3.0.7
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 +1 -1
- package/src/render.ts +50 -37
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/render.ts
CHANGED
|
@@ -249,7 +249,7 @@ export function init(opts: {
|
|
|
249
249
|
}
|
|
250
250
|
flushDrainedThisPass = 0;
|
|
251
251
|
renderNoProgressStreak = 0;
|
|
252
|
-
|
|
252
|
+
syncCursorBlink();
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
/**
|
|
@@ -739,7 +739,11 @@ function flushRenderInner(): void {
|
|
|
739
739
|
cursorCol = win.cursorCol;
|
|
740
740
|
cursorHidden = win.cursorHidden;
|
|
741
741
|
cursorStyleVal = win.cursorStyle;
|
|
742
|
-
|
|
742
|
+
blinkEnabled = win.cursorBlink;
|
|
743
|
+
// Reconcile the blink interval with the fresh cursor state (blink mode and
|
|
744
|
+
// DECTCM visibility may both have changed this frame); no-op when the
|
|
745
|
+
// resulting mode is unchanged.
|
|
746
|
+
syncCursorBlink();
|
|
743
747
|
|
|
744
748
|
// Alt screen: render the ephemeral grid instead of the absolute buffer.
|
|
745
749
|
// Returning here skips the dirtyLines queueing below — safe by invariant:
|
|
@@ -954,8 +958,20 @@ function stickToBottomIfFollowing(): void {
|
|
|
954
958
|
|
|
955
959
|
// --- Cursor blink ---
|
|
956
960
|
const CURSOR_BLINK_MS = 530;
|
|
961
|
+
// While the application hides the cursor (DECTCM — e.g. a full-screen TUI
|
|
962
|
+
// that paints its own cursor cell, like an agent front-end), the fast toggle
|
|
963
|
+
// would only restyle a display:none overlay, so the interval downshifts to
|
|
964
|
+
// this slow re-check: still polling for the cursor's return (a self-healing
|
|
965
|
+
// backstop even if a transition frame were somehow missed — the flush hook is
|
|
966
|
+
// the primary, instant restart), at a fraction of the 530ms blink's wakeups.
|
|
967
|
+
// For agent consumers the hidden-cursor state is the session's steady state.
|
|
968
|
+
const CURSOR_RECHECK_MS = 4000;
|
|
957
969
|
let blinkInterval: ReturnType<typeof setInterval> | null = null;
|
|
958
970
|
let blinkEnabled = true;
|
|
971
|
+
// What the running interval (if any) is configured as. syncCursorBlink()
|
|
972
|
+
// reconfigures only on mode change, so it is safe to call on every flush.
|
|
973
|
+
type BlinkMode = "off" | "idle" | "fast";
|
|
974
|
+
let blinkMode: BlinkMode = "off";
|
|
959
975
|
|
|
960
976
|
// hiddenDoc reports whether the page is currently background/hidden. The blink
|
|
961
977
|
// interval is gated on it: rAF-driven flushes already freeze in a hidden tab,
|
|
@@ -965,25 +981,42 @@ function hiddenDoc(): boolean {
|
|
|
965
981
|
return typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
966
982
|
}
|
|
967
983
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
984
|
+
// desiredBlinkMode folds the three gates into an interval mode: no timer at
|
|
985
|
+
// all when blinking is disabled or the page is hidden (both have event-driven
|
|
986
|
+
// restarts — the next mode frame's flush and visibilitychange), the fast
|
|
987
|
+
// phase toggle when the cursor is visible, and the slow re-check while the
|
|
988
|
+
// application hides it.
|
|
989
|
+
function desiredBlinkMode(): BlinkMode {
|
|
990
|
+
if (!blinkEnabled || hiddenDoc()) {
|
|
991
|
+
return "off";
|
|
971
992
|
}
|
|
972
|
-
|
|
973
|
-
// (not inside output), and the `.cursor-blink-off .term-cursor` descendant
|
|
974
|
-
// selector must reach it.
|
|
975
|
-
termWrap.classList.remove("cursor-blink-off");
|
|
976
|
-
blinkInterval = setInterval(() => {
|
|
977
|
-
termWrap.classList.toggle("cursor-blink-off");
|
|
978
|
-
}, CURSOR_BLINK_MS);
|
|
993
|
+
return cursorHidden ? "idle" : "fast";
|
|
979
994
|
}
|
|
980
995
|
|
|
981
|
-
|
|
996
|
+
// syncCursorBlink reconciles the interval with the current gate state; every
|
|
997
|
+
// mode change resets the phase to solid. Called from the flush (blink mode +
|
|
998
|
+
// DECTCM state), the visibilitychange listener, init(), and the idle re-check
|
|
999
|
+
// itself. The blink class lives on termWrap: the caret overlay is a termWrap
|
|
1000
|
+
// child (not inside output), and the `.cursor-blink-off .term-cursor`
|
|
1001
|
+
// descendant selector must reach it.
|
|
1002
|
+
function syncCursorBlink(): void {
|
|
1003
|
+
const mode = desiredBlinkMode();
|
|
1004
|
+
if (mode === blinkMode) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
blinkMode = mode;
|
|
982
1008
|
if (blinkInterval !== null) {
|
|
983
1009
|
clearInterval(blinkInterval);
|
|
984
1010
|
blinkInterval = null;
|
|
985
1011
|
}
|
|
986
1012
|
termWrap.classList.remove("cursor-blink-off");
|
|
1013
|
+
if (mode === "fast") {
|
|
1014
|
+
blinkInterval = setInterval(() => {
|
|
1015
|
+
termWrap.classList.toggle("cursor-blink-off");
|
|
1016
|
+
}, CURSOR_BLINK_MS);
|
|
1017
|
+
} else if (mode === "idle") {
|
|
1018
|
+
blinkInterval = setInterval(syncCursorBlink, CURSOR_RECHECK_MS);
|
|
1019
|
+
}
|
|
987
1020
|
}
|
|
988
1021
|
|
|
989
1022
|
// Pause the interval while hidden; resume (cursor solid, phase reset) when the
|
|
@@ -995,33 +1028,13 @@ if (typeof document !== "undefined") {
|
|
|
995
1028
|
if ((termWrap as HTMLElement | undefined) === undefined) {
|
|
996
1029
|
return;
|
|
997
1030
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
clearInterval(blinkInterval);
|
|
1003
|
-
blinkInterval = null;
|
|
1004
|
-
}
|
|
1005
|
-
termWrap.classList.remove("cursor-blink-off");
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
startCursorBlink();
|
|
1031
|
+
// blinkEnabled/cursorHidden keep their server-driven state across a
|
|
1032
|
+
// background stint, so foregrounding restores the right mode without
|
|
1033
|
+
// waiting for the next frame.
|
|
1034
|
+
syncCursorBlink();
|
|
1009
1035
|
});
|
|
1010
1036
|
}
|
|
1011
1037
|
|
|
1012
|
-
/** Called from the flush when cursorBlink state changes. */
|
|
1013
|
-
function setCursorBlink(enabled: boolean): void {
|
|
1014
|
-
if (enabled === blinkEnabled) {
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
blinkEnabled = enabled;
|
|
1018
|
-
if (enabled) {
|
|
1019
|
-
startCursorBlink();
|
|
1020
|
-
} else {
|
|
1021
|
-
stopCursorBlink();
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
1038
|
// --- Font metrics & sizing ---
|
|
1026
1039
|
/**
|
|
1027
1040
|
* Re-measure the cell width/height from the rendered DOM. Call after any font
|