@deepseek-harness-tui/dsh-tui 0.8.7 → 0.9.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/README.md +29 -10
- package/bin/dsh-tui.js +1 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +4 -2
- package/lib/types/components/ActivityPicker.d.ts +4 -1
- package/lib/types/components/ActivityPicker.d.ts.map +1 -1
- package/lib/types/components/ActivityPicker.js +2 -2
- package/lib/types/components/BtwPanel.d.ts.map +1 -1
- package/lib/types/components/BtwPanel.js +1 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +2 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -1
- package/lib/types/components/Chat/SubagentMessage.js +11 -3
- package/lib/types/components/CommandSuggestions.d.ts +21 -4
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +41 -14
- package/lib/types/components/EffortSlider.d.ts +4 -1
- package/lib/types/components/EffortSlider.d.ts.map +1 -1
- package/lib/types/components/EffortSlider.js +4 -2
- package/lib/types/components/ExtensionDialog.js +6 -2
- package/lib/types/components/FileSuggestions.d.ts +26 -5
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/FileSuggestions.js +53 -16
- package/lib/types/components/GoalTodoPanel.d.ts +26 -2
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +86 -17
- package/lib/types/components/HelpMenu.d.ts +7 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +12 -4
- package/lib/types/components/HistorySearchDialog.d.ts +4 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +2 -2
- package/lib/types/components/LangPicker.d.ts +14 -0
- package/lib/types/components/LangPicker.d.ts.map +1 -0
- package/lib/types/components/LangPicker.js +17 -0
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +3 -1
- package/lib/types/components/LogoV2.d.ts +5 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +12 -3
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +12 -3
- package/lib/types/components/MessageList.d.ts +43 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +553 -41
- package/lib/types/components/ModelPicker.d.ts +4 -1
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PermissionsPicker.d.ts +21 -0
- package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
- package/lib/types/components/PermissionsPicker.js +27 -0
- package/lib/types/components/PlanPicker.d.ts +15 -0
- package/lib/types/components/PlanPicker.d.ts.map +1 -0
- package/lib/types/components/PlanPicker.js +18 -0
- package/lib/types/components/PresetPicker.d.ts +4 -1
- package/lib/types/components/PresetPicker.d.ts.map +1 -1
- package/lib/types/components/PresetPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +4 -3
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +273 -50
- package/lib/types/components/RewindPicker.d.ts +12 -1
- package/lib/types/components/RewindPicker.d.ts.map +1 -1
- package/lib/types/components/RewindPicker.js +6 -4
- package/lib/types/components/ScrollbarGutter.d.ts +28 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
- package/lib/types/components/ScrollbarGutter.js +68 -0
- package/lib/types/components/Select.d.ts +8 -1
- package/lib/types/components/Select.d.ts.map +1 -1
- package/lib/types/components/Select.js +4 -2
- package/lib/types/components/SkillsPicker.d.ts +4 -1
- package/lib/types/components/SkillsPicker.d.ts.map +1 -1
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SubagentCard.d.ts +2 -1
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -1
- package/lib/types/components/SubagentDashboard.d.ts +4 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
- package/lib/types/components/SubagentDashboard.js +9 -1
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
- package/lib/types/components/SubagentDetailScene.js +5 -4
- package/lib/types/components/SuggestionCard.d.ts +63 -0
- package/lib/types/components/SuggestionCard.d.ts.map +1 -0
- package/lib/types/components/SuggestionCard.js +77 -0
- package/lib/types/components/ThemePicker.d.ts +4 -1
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +2 -2
- package/lib/types/components/ThinkingToggle.d.ts +4 -1
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +2 -2
- package/lib/types/components/TimelineRail.d.ts +53 -0
- package/lib/types/components/TimelineRail.d.ts.map +1 -0
- package/lib/types/components/TimelineRail.js +190 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
- package/lib/types/components/WorkspaceFlowPicker.js +4 -2
- package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
- package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceMenuPicker.js +19 -0
- package/lib/types/components/WorkspacePicker.d.ts +4 -1
- package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
- package/lib/types/components/WorkspacePicker.js +2 -2
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +5 -1
- package/lib/types/components/design-system/ListItem.d.ts +8 -1
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +8 -3
- package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantTextMessage.js +6 -2
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +4 -2
- package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +34 -2
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +13 -2
- package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +4 -2
- package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
- package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
- package/lib/types/components/trajectory/HotspotView.js +8 -2
- package/lib/types/components/trajectory/Ledger.d.ts +9 -1
- package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
- package/lib/types/components/trajectory/Ledger.js +23 -5
- package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
- package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
- package/lib/types/components/trajectory/WaveBand.js +7 -2
- package/lib/types/dsh-adapter/channel.d.ts +27 -6
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +432 -66
- package/lib/types/dsh-adapter/contract.d.ts +58 -18
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +124 -36
- package/lib/types/dsh-adapter/index.d.ts +10 -2
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +10 -13
- package/lib/types/dsh-adapter/plugin.d.ts +0 -9
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +136 -6
- package/lib/types/i18n.d.ts +194 -16
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +73 -10
- package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
- package/lib/types/ink/components/AlternateScreen.js +13 -1
- package/lib/types/ink/components/App.d.ts +12 -1
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +101 -4
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
- package/lib/types/ink/components/ClockContext.js +2 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +170 -91
- package/lib/types/ink/constants.d.ts +9 -0
- package/lib/types/ink/constants.d.ts.map +1 -1
- package/lib/types/ink/constants.js +9 -0
- package/lib/types/ink/events/click-event.d.ts +7 -16
- package/lib/types/ink/events/click-event.d.ts.map +1 -1
- package/lib/types/ink/events/click-event.js +16 -19
- package/lib/types/ink/events/event-handlers.d.ts +11 -1
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
- package/lib/types/ink/events/event-handlers.js +2 -0
- package/lib/types/ink/events/input-event.d.ts +9 -0
- package/lib/types/ink/events/input-event.d.ts.map +1 -1
- package/lib/types/ink/events/input-event.js +4 -0
- package/lib/types/ink/events/pointer-event.d.ts +61 -0
- package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
- package/lib/types/ink/events/pointer-event.js +61 -0
- package/lib/types/ink/events/wheel-event.d.ts +17 -0
- package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
- package/lib/types/ink/events/wheel-event.js +20 -0
- package/lib/types/ink/geometry-trace.d.ts +51 -0
- package/lib/types/ink/geometry-trace.d.ts.map +1 -0
- package/lib/types/ink/geometry-trace.js +83 -0
- package/lib/types/ink/hit-test.d.ts +56 -1
- package/lib/types/ink/hit-test.d.ts.map +1 -1
- package/lib/types/ink/hit-test.js +146 -8
- package/lib/types/ink/ink.d.ts +58 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +268 -52
- package/lib/types/ink/node-cache.d.ts +5 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.d.ts +8 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +101 -12
- package/lib/types/ink/render-node-to-output.d.ts +16 -3
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +144 -25
- package/lib/types/ink/selection.d.ts +29 -4
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +41 -5
- package/lib/types/ink/timeline-rail.d.ts +138 -0
- package/lib/types/ink/timeline-rail.d.ts.map +1 -0
- package/lib/types/ink/timeline-rail.js +179 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -1
- package/lib/types/ink/wrap-text.js +47 -5
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +819 -337
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +56 -29
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +64 -22
- package/lib/types/screens/StatusLine.d.ts +2 -1
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +17 -1
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +81 -14
- package/lib/types/screens/chatOverlay.d.ts +221 -0
- package/lib/types/screens/chatOverlay.d.ts.map +1 -0
- package/lib/types/screens/chatOverlay.js +114 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +64 -10
- package/lib/types/tuiDisplayPrefs.d.ts +15 -0
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +9 -0
- package/lib/types/utils/debug.d.ts +10 -0
- package/lib/types/utils/debug.d.ts.map +1 -1
- package/lib/types/utils/debug.js +24 -0
- package/lib/types/utils/fileSuggestions.d.ts +19 -0
- package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
- package/lib/types/utils/fileSuggestions.js +40 -0
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +3 -1
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +1 -0
- package/package.json +69 -66
|
@@ -610,7 +610,12 @@ export function parseMultipleKeypresses(prevState, input = '') {
|
|
|
610
610
|
keys.push({ kind: 'response', sequence: token.value, response });
|
|
611
611
|
}
|
|
612
612
|
else {
|
|
613
|
-
|
|
613
|
+
// SGR first (1006); X10 (legacy 1000/1002 without SGR) as the
|
|
614
|
+
// compatibility fallback for clicks/drags. Wheel falls through
|
|
615
|
+
// to parseKeypress, which turns it into a wheel key WITH the
|
|
616
|
+
// pointer coordinates for position-based routing.
|
|
617
|
+
const mouse = parseMouseEvent(token.value) ??
|
|
618
|
+
parseX10MouseEvent(token.value);
|
|
614
619
|
if (mouse) {
|
|
615
620
|
keys.push(mouse);
|
|
616
621
|
}
|
|
@@ -819,6 +824,8 @@ export const nonAlphanumericKeys = [
|
|
|
819
824
|
'backspace',
|
|
820
825
|
'wheelup',
|
|
821
826
|
'wheeldown',
|
|
827
|
+
'wheelleft',
|
|
828
|
+
'wheelright',
|
|
822
829
|
'mouse',
|
|
823
830
|
];
|
|
824
831
|
const isShiftKey = (code) => {
|
|
@@ -955,6 +962,39 @@ function parseMouseEvent(s) {
|
|
|
955
962
|
sequence: s,
|
|
956
963
|
};
|
|
957
964
|
}
|
|
965
|
+
/**
|
|
966
|
+
* Parse an X10 mouse sequence (CSI M + Cb/Cx/Cy raw bytes, each +32) into
|
|
967
|
+
* a ParsedMouse, or null for anything else. Serves terminals that honor
|
|
968
|
+
* DECSET 1000/1002 but ignore 1006 (SGR): clicks and button-drags become
|
|
969
|
+
* real ParsedMouse events so text selection works there too.
|
|
970
|
+
*
|
|
971
|
+
* Protocol limits (documented, accepted): X10 has no release events — a
|
|
972
|
+
* started selection finishes via App's lost-release recovery (next press /
|
|
973
|
+
* focus-out / no-button motion), and click dispatch (release-based) stays
|
|
974
|
+
* SGR-only. No-button motion (hover) is unrepresentable without the drag
|
|
975
|
+
* bit, so hover stays SGR-only. Wheel returns null (parseKeypress's wheel
|
|
976
|
+
* branch turns it into a key with coordinates).
|
|
977
|
+
*/
|
|
978
|
+
function parseX10MouseEvent(s) {
|
|
979
|
+
if (s.length !== 6 || !s.startsWith('\x1b[M'))
|
|
980
|
+
return null;
|
|
981
|
+
const button = s.charCodeAt(3) - 32;
|
|
982
|
+
// Wheel (bit 6) → key path; no-button without drag bit → unsupported
|
|
983
|
+
// hover encoding, swallow upstream.
|
|
984
|
+
if ((button & 0x40) !== 0)
|
|
985
|
+
return null;
|
|
986
|
+
if ((button & 0x03) === 3 && (button & 0x20) === 0)
|
|
987
|
+
return null;
|
|
988
|
+
return {
|
|
989
|
+
kind: 'mouse',
|
|
990
|
+
button,
|
|
991
|
+
action: 'press',
|
|
992
|
+
// X10 coords are 1-indexed like SGR (charCode - 32).
|
|
993
|
+
col: s.charCodeAt(4) - 32,
|
|
994
|
+
row: s.charCodeAt(5) - 32,
|
|
995
|
+
sequence: s,
|
|
996
|
+
};
|
|
997
|
+
}
|
|
958
998
|
function parseKeypress(s = '') {
|
|
959
999
|
let parts;
|
|
960
1000
|
const key = {
|
|
@@ -1020,26 +1060,53 @@ function parseKeypress(s = '') {
|
|
|
1020
1060
|
// + direction while ignoring modifier bits (Shift=0x04, Meta=0x08,
|
|
1021
1061
|
// Ctrl=0x10) — modified wheel events (e.g. Ctrl+scroll, button=80)
|
|
1022
1062
|
// should still be recognized as wheelup/wheeldown.
|
|
1063
|
+
//
|
|
1064
|
+
// The SGR sequence carries the pointer position (CSI < btn;col;row M) —
|
|
1065
|
+
// preserved on the ParsedKey (mouseCol/mouseRow, 0-indexed) so wheel
|
|
1066
|
+
// routing can hit-test the ScrollBox under the pointer instead of
|
|
1067
|
+
// scrolling a hardcoded target. Buttons 66/67 are horizontal wheel
|
|
1068
|
+
// (wheelleft/wheelright); kept as keys with coords for future consumers.
|
|
1023
1069
|
if ((match = SGR_MOUSE_RE.exec(s))) {
|
|
1024
1070
|
const button = parseInt(match[1], 10);
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1071
|
+
const col = parseInt(match[2], 10) - 1;
|
|
1072
|
+
const row = parseInt(match[3], 10) - 1;
|
|
1073
|
+
const dir = button & 0x43;
|
|
1074
|
+
if (dir === 0x40)
|
|
1075
|
+
return createWheelKey(s, 'wheelup', col, row, button);
|
|
1076
|
+
if (dir === 0x41)
|
|
1077
|
+
return createWheelKey(s, 'wheeldown', col, row, button);
|
|
1078
|
+
if (dir === 0x42)
|
|
1079
|
+
return createWheelKey(s, 'wheelleft', col, row, button);
|
|
1080
|
+
if (dir === 0x43)
|
|
1081
|
+
return createWheelKey(s, 'wheelright', col, row, button);
|
|
1029
1082
|
// Shouldn't reach here (parseMouseEvent catches non-wheel) but be safe
|
|
1030
1083
|
return createNavKey(s, 'mouse', false);
|
|
1031
1084
|
}
|
|
1032
1085
|
// X10 mouse: CSI M + 3 raw bytes (Cb+32, Cx+32, Cy+32). Terminals that
|
|
1033
1086
|
// ignore DECSET 1006 (SGR) but honor 1000/1002 emit this legacy encoding.
|
|
1034
|
-
// Button bits match SGR: 0x40 = wheel, low bit = direction.
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1087
|
+
// Button bits match SGR: 0x40 = wheel, low bit = direction, 0x20 = drag.
|
|
1088
|
+
// Wheel events become wheel keys (with coordinates, like SGR); clicks and
|
|
1089
|
+
// drags become ParsedMouse so X10-only terminals get selection support.
|
|
1090
|
+
// X10 cannot report button release — click dispatch (release-based) stays
|
|
1091
|
+
// SGR-only; a started selection finishes via the lost-release recovery
|
|
1092
|
+
// paths (next press / focus-out / no-button motion).
|
|
1037
1093
|
if (s.length === 6 && s.startsWith('\x1b[M')) {
|
|
1038
1094
|
const button = s.charCodeAt(3) - 32;
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1095
|
+
const col = s.charCodeAt(4) - 32 - 1;
|
|
1096
|
+
const row = s.charCodeAt(5) - 32 - 1;
|
|
1097
|
+
const dir = button & 0x43;
|
|
1098
|
+
if (dir === 0x40)
|
|
1099
|
+
return createWheelKey(s, 'wheelup', col, row, button);
|
|
1100
|
+
if (dir === 0x41)
|
|
1101
|
+
return createWheelKey(s, 'wheeldown', col, row, button);
|
|
1102
|
+
if (dir === 0x42)
|
|
1103
|
+
return createWheelKey(s, 'wheelleft', col, row, button);
|
|
1104
|
+
if (dir === 0x43)
|
|
1105
|
+
return createWheelKey(s, 'wheelright', col, row, button);
|
|
1106
|
+
// Non-wheel X10 clicks/drags are intercepted by parseX10MouseEvent in
|
|
1107
|
+
// parseMultipleKeypresses before this function runs; reaching here means
|
|
1108
|
+
// a flush path bypassed it (no-button motion without the drag bit — X10
|
|
1109
|
+
// hover, unsupported). Swallow as before.
|
|
1043
1110
|
return createNavKey(s, 'mouse', false);
|
|
1044
1111
|
}
|
|
1045
1112
|
if (s === '\r') {
|
|
@@ -1157,3 +1224,25 @@ function createNavKey(s, name, ctrl) {
|
|
|
1157
1224
|
isPasted: false,
|
|
1158
1225
|
};
|
|
1159
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Build a wheel ParsedKey preserving the pointer position and modifier
|
|
1229
|
+
* bits from the protocol's button byte (Shift=0x04, Meta/Alt=0x08,
|
|
1230
|
+
* Ctrl=0x10 — same bit layout decodeModifier documents).
|
|
1231
|
+
*/
|
|
1232
|
+
function createWheelKey(s, name, col, row, button) {
|
|
1233
|
+
return {
|
|
1234
|
+
kind: 'key',
|
|
1235
|
+
name,
|
|
1236
|
+
ctrl: (button & 0x10) !== 0,
|
|
1237
|
+
meta: (button & 0x08) !== 0,
|
|
1238
|
+
shift: (button & 0x04) !== 0,
|
|
1239
|
+
option: false,
|
|
1240
|
+
super: false,
|
|
1241
|
+
fn: false,
|
|
1242
|
+
sequence: s,
|
|
1243
|
+
raw: s,
|
|
1244
|
+
isPasted: false,
|
|
1245
|
+
mouseCol: col,
|
|
1246
|
+
mouseRow: row,
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DOMElement } from './dom.js';
|
|
2
|
+
import type { Rectangle } from './layout/geometry.js';
|
|
2
3
|
import type Output from './output.js';
|
|
3
4
|
import type { Screen } from './screen.js';
|
|
4
5
|
import { type StyledSegment } from './squash-text-nodes.js';
|
|
@@ -22,6 +23,15 @@ export type ScrollHint = {
|
|
|
22
23
|
bottom: number;
|
|
23
24
|
delta: number;
|
|
24
25
|
};
|
|
26
|
+
export type AbsoluteHitEntry = {
|
|
27
|
+
node: DOMElement;
|
|
28
|
+
rect: Rectangle;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The current frame's absolute-positioned nodes in paint order.
|
|
32
|
+
* @returns read-only list; reverse-iterate for topmost-first hit-testing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getAbsoluteHitList(): readonly AbsoluteHitEntry[];
|
|
25
35
|
/** Reset the scroll hint for the next frame and rotate the absolute-rect buffers. */
|
|
26
36
|
export declare function resetScrollHint(): void;
|
|
27
37
|
/**
|
|
@@ -47,10 +57,13 @@ export type FollowScroll = {
|
|
|
47
57
|
viewportBottom: number;
|
|
48
58
|
};
|
|
49
59
|
/**
|
|
50
|
-
* Read and clear the follow-scroll
|
|
51
|
-
*
|
|
60
|
+
* Read and clear the follow-scroll events recorded this frame. At most one
|
|
61
|
+
* per ScrollBox (a box that follows AND drains reports only the follow —
|
|
62
|
+
* the follow branch clears pendingScrollDelta before the drain runs);
|
|
63
|
+
* several boxes may each report when they scroll in the same frame.
|
|
64
|
+
* @returns this frame's follow-scroll events; empty when none.
|
|
52
65
|
*/
|
|
53
|
-
export declare function consumeFollowScroll(): FollowScroll
|
|
66
|
+
export declare function consumeFollowScroll(): FollowScroll[];
|
|
54
67
|
/**
|
|
55
68
|
* Build a mapping from each character position in the plain text to its segment index.
|
|
56
69
|
* Returns an array where charToSegment[i] is the segment index for character i.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAqBxC,6CAA6C;AAC7C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAOD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAkBvE,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAGpE;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,gBAAgB,EAAE,CAEhE;AAED,qFAAqF;AACrF,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AASD,8DAA8D;AAC9D,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,GAAG,IAAI,CAEtD;AAgBD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAGD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,YAAY,EAAE,CAIpD;AAoFD;;;;;GAKG;AACH,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CASlE;AAED;;;;;;;;;;;GAWG;AACH,iBAAS,wBAAwB,CAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,EAAE,EACzB,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,OAAe,GAC3B,MAAM,CA0GR;AA+DD;;;;;;;;;GASG;AACH,iBAAS,kBAAkB,CACzB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAM9B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,wBAAwB,CAAC,EAAE,KAAK,CAAA;CACjC,GACA,IAAI,CAq9BN;AA0OD,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,CAAA;AAE1D,eAAe,kBAAkB,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import indentString from 'indent-string';
|
|
2
2
|
import { applyTextStyles } from './colorize.js';
|
|
3
|
+
import { GEOMETRY_TRACE_ENABLED, noteScrollGeometry } from './geometry-trace.js';
|
|
3
4
|
import getMaxWidth from './get-max-width.js';
|
|
4
5
|
import { LayoutDisplay, LayoutEdge } from './layout/node.js';
|
|
5
6
|
import { nodeCache, pendingClears } from './node-cache.js';
|
|
@@ -41,11 +42,20 @@ let scrollHint = null;
|
|
|
41
42
|
// still have the rect.
|
|
42
43
|
let absoluteRectsPrev = [];
|
|
43
44
|
let absoluteRectsCur = [];
|
|
45
|
+
let absoluteHitList = [];
|
|
46
|
+
/**
|
|
47
|
+
* The current frame's absolute-positioned nodes in paint order.
|
|
48
|
+
* @returns read-only list; reverse-iterate for topmost-first hit-testing.
|
|
49
|
+
*/
|
|
50
|
+
export function getAbsoluteHitList() {
|
|
51
|
+
return absoluteHitList;
|
|
52
|
+
}
|
|
44
53
|
/** Reset the scroll hint for the next frame and rotate the absolute-rect buffers. */
|
|
45
54
|
export function resetScrollHint() {
|
|
46
55
|
scrollHint = null;
|
|
47
56
|
absoluteRectsPrev = absoluteRectsCur;
|
|
48
57
|
absoluteRectsCur = [];
|
|
58
|
+
absoluteHitList = [];
|
|
49
59
|
}
|
|
50
60
|
/**
|
|
51
61
|
* The scroll hint captured this frame, or null.
|
|
@@ -71,14 +81,17 @@ export function resetScrollDrainNode() {
|
|
|
71
81
|
export function getScrollDrainNode() {
|
|
72
82
|
return scrollDrainNode;
|
|
73
83
|
}
|
|
74
|
-
let
|
|
84
|
+
let followScrolls = [];
|
|
75
85
|
/**
|
|
76
|
-
* Read and clear the follow-scroll
|
|
77
|
-
*
|
|
86
|
+
* Read and clear the follow-scroll events recorded this frame. At most one
|
|
87
|
+
* per ScrollBox (a box that follows AND drains reports only the follow —
|
|
88
|
+
* the follow branch clears pendingScrollDelta before the drain runs);
|
|
89
|
+
* several boxes may each report when they scroll in the same frame.
|
|
90
|
+
* @returns this frame's follow-scroll events; empty when none.
|
|
78
91
|
*/
|
|
79
92
|
export function consumeFollowScroll() {
|
|
80
|
-
const f =
|
|
81
|
-
|
|
93
|
+
const f = followScrolls;
|
|
94
|
+
followScrolls = [];
|
|
82
95
|
return f;
|
|
83
96
|
}
|
|
84
97
|
// ── Native terminal drain (iTerm2/Ghostty/etc. — proportional events) ──
|
|
@@ -356,18 +369,34 @@ function renderNodeToOutput(node, output, options) {
|
|
|
356
369
|
let y = offsetY + yogaTop;
|
|
357
370
|
const width = yogaNode.getComputedWidth();
|
|
358
371
|
const height = yogaNode.getComputedHeight();
|
|
359
|
-
// Absolute-positioned overlays
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
// Absolute-positioned overlays anchored above their parent
|
|
373
|
+
// (bottom='100%') compute negative screen y when their content is
|
|
374
|
+
// taller than the space above the anchor. Do NOT clamp y to 0 here:
|
|
375
|
+
// shifting the node down breaks the bottom-anchoring contract — the
|
|
376
|
+
// panel's BOTTOM would overflow past the anchor and paint over the
|
|
377
|
+
// composer/status rows below it (small-terminal overlay reports).
|
|
378
|
+
// Instead let the y<0 rows clip naturally: setCellAt drops cells above
|
|
379
|
+
// row 0 and blitRegion clamps in screen space, so the panel keeps its
|
|
380
|
+
// bottom pinned at the anchor and only loses its topmost rows — the
|
|
381
|
+
// same semantics as CSS clipping above the containing block. Producers
|
|
382
|
+
// prevent the case entirely by maxHeight-ing the overlay to the actual
|
|
383
|
+
// space above the anchor (OverlayAbove), so clipping is the last
|
|
384
|
+
// resort, not the layout.
|
|
368
385
|
// Check if we can skip this subtree (clean node with unchanged layout).
|
|
369
386
|
// Blit cells from previous screen instead of re-rendering.
|
|
370
387
|
const cached = nodeCache.get(node);
|
|
388
|
+
// The node's EFFECTIVE background = its own + the inherited one. When it
|
|
389
|
+
// changed since the last frame (hover on/off toggles a row's
|
|
390
|
+
// backgroundColor, a parent's bg swap changes every child's inherited
|
|
391
|
+
// value), prevScreen still holds the OLD background — a clean child
|
|
392
|
+
// blitting it would resurrect the stale color (stuck hover highlights:
|
|
393
|
+
// the row's fill is skipped once the bg is removed, its clean children
|
|
394
|
+
// then blit the previous bg'd cells, the frame equals prevScreen, the
|
|
395
|
+
// diff finds nothing, and the highlight never clears). Compare against
|
|
396
|
+
// the value recorded at the previous render and refuse the blit when it
|
|
397
|
+
// moved.
|
|
398
|
+
const effectiveBg = node.style.backgroundColor ?? inheritedBackgroundColor;
|
|
399
|
+
const bgChanged = cached?.bg !== effectiveBg;
|
|
371
400
|
if (!node.dirty &&
|
|
372
401
|
!skipSelfBlit &&
|
|
373
402
|
node.pendingScrollDelta === undefined &&
|
|
@@ -376,6 +405,7 @@ function renderNodeToOutput(node, output, options) {
|
|
|
376
405
|
cached.y === y &&
|
|
377
406
|
cached.width === width &&
|
|
378
407
|
cached.height === height &&
|
|
408
|
+
!bgChanged &&
|
|
379
409
|
prevScreen) {
|
|
380
410
|
const fx = Math.floor(x);
|
|
381
411
|
const fy = Math.floor(y);
|
|
@@ -384,6 +414,7 @@ function renderNodeToOutput(node, output, options) {
|
|
|
384
414
|
output.blit(prevScreen, fx, fy, fw, fh);
|
|
385
415
|
if (node.style.position === 'absolute') {
|
|
386
416
|
absoluteRectsCur.push(cached);
|
|
417
|
+
absoluteHitList.push({ node, rect: cached });
|
|
387
418
|
}
|
|
388
419
|
// Absolute descendants can paint outside this node's layout bounds
|
|
389
420
|
// (e.g. a slash menu with position='absolute' bottom='100%' floats
|
|
@@ -590,7 +621,29 @@ function renderNodeToOutput(node, output, options) {
|
|
|
590
621
|
// within the viewport (equal to the scroll container's
|
|
591
622
|
// paddingTop), and innerHeight already subtracts padding, so
|
|
592
623
|
// including it double-counts padding and inflates maxScroll.
|
|
593
|
-
|
|
624
|
+
let scrollHeight = contentYoga?.getComputedHeight() ?? 0;
|
|
625
|
+
// Defensive extent floor: the wrapper's Yoga height can land ONE LINE
|
|
626
|
+
// short of its children's laid-out extent (the engine's flex-basis
|
|
627
|
+
// measure cache and the child's final subtree layout can disagree by
|
|
628
|
+
// a row — measured on resume of a long session: wrapper 286 while the
|
|
629
|
+
// last row's bottom sat at 287, stable across forced re-layouts). A
|
|
630
|
+
// short scrollHeight clamps maxScroll below the real bottom, so the
|
|
631
|
+
// sticky pin parks the viewport a line up and the tail line culls
|
|
632
|
+
// against the viewport edge — permanently invisible AND unreachable
|
|
633
|
+
// (no scroll position ever shows it; the user loses the newest
|
|
634
|
+
// message). The children's real extent is the authoritative floor.
|
|
635
|
+
// O(direct children) — the windowed list keeps this tiny, and the
|
|
636
|
+
// painter walks the same nodes right below.
|
|
637
|
+
if (content !== undefined) {
|
|
638
|
+
for (const child of content.childNodes) {
|
|
639
|
+
const childYoga = child.yogaNode;
|
|
640
|
+
if (childYoga === undefined)
|
|
641
|
+
continue;
|
|
642
|
+
const bottom = childYoga.getComputedTop() + childYoga.getComputedHeight();
|
|
643
|
+
if (bottom > scrollHeight)
|
|
644
|
+
scrollHeight = bottom;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
594
647
|
// Capture previous scroll bounds BEFORE overwriting — the at-bottom
|
|
595
648
|
// follow check compares against last frame's max.
|
|
596
649
|
const prevScrollHeight = node.scrollHeight ?? scrollHeight;
|
|
@@ -684,11 +737,11 @@ function renderNodeToOutput(node, output, options) {
|
|
|
684
737
|
const followDelta = (node.scrollTop ?? 0) - scrollTopBeforeFollow;
|
|
685
738
|
if (followDelta > 0) {
|
|
686
739
|
const vpTop = node.scrollViewportTop ?? 0;
|
|
687
|
-
|
|
740
|
+
followScrolls.push({
|
|
688
741
|
delta: followDelta,
|
|
689
742
|
viewportTop: vpTop,
|
|
690
743
|
viewportBottom: vpTop + innerHeight - 1,
|
|
691
|
-
};
|
|
744
|
+
});
|
|
692
745
|
}
|
|
693
746
|
// Drain pendingScrollDelta. Native terminals (proportional burst
|
|
694
747
|
// events) use proportional drain; xterm.js (VS Code, sparse events +
|
|
@@ -732,7 +785,17 @@ function renderNodeToOutput(node, output, options) {
|
|
|
732
785
|
// Keep the pre-frame position on a shrink frame (measurement
|
|
733
786
|
// artifact) instead of clamping to the shrunken maxScroll — the
|
|
734
787
|
// clamp would persist the yank even after content grows back.
|
|
735
|
-
|
|
788
|
+
// Exception: a STICKY view must stay pinned to the bottom. A width
|
|
789
|
+
// change clears MessageList's row-height cache, collapsing the
|
|
790
|
+
// estimated scrollHeight; freezing the pre-shrink scrollTop then
|
|
791
|
+
// parks it PAST the whole shrunken content — every child culls and
|
|
792
|
+
// the frame paints only the bottom chrome. With the resize erase
|
|
793
|
+
// (needsEraseBeforePaint) that lands as a pure blank screen with
|
|
794
|
+
// just the input box, and an idle app produces no follow-up frame
|
|
795
|
+
// to heal it (user-reported full-screen loss on pane-width jitter —
|
|
796
|
+
// issue #421; repro-resize-blank). Clamping a sticky view to the shrunken
|
|
797
|
+
// maxScroll is exactly its contract — show the bottom.
|
|
798
|
+
let scrollTop = shrunk && !sticky ? cur : Math.max(0, Math.min(cur, maxScroll));
|
|
736
799
|
// Virtual-scroll clamp: if scrollTop raced past the currently-mounted
|
|
737
800
|
// range (burst PageUp before React re-renders), render at the EDGE of
|
|
738
801
|
// the mounted children instead of blank spacer. Do NOT write back to
|
|
@@ -741,7 +804,15 @@ function renderNodeToOutput(node, output, options) {
|
|
|
741
804
|
// the right range. Not scheduling scrollDrainNode here keeps the
|
|
742
805
|
// clamp passive — React's commit → resetAfterCommit → onRender will
|
|
743
806
|
// paint again with fresh bounds.
|
|
744
|
-
|
|
807
|
+
// Cap both bounds at maxScroll: the clamp bounds come from
|
|
808
|
+
// MessageList's height ESTIMATES while maxScroll comes from the
|
|
809
|
+
// frame's actual Yoga height. A width change resets the estimates
|
|
810
|
+
// (row-height cache clear), and a stale bound can point PAST the
|
|
811
|
+
// shrunken content bottom — the painted viewport then sits entirely
|
|
812
|
+
// below the content and every child culls to blank (the sticky
|
|
813
|
+
// full-screen loss — issue #421; repro-resize-blank). Nothing renderable exists
|
|
814
|
+
// below maxScroll regardless of what the estimates claim.
|
|
815
|
+
const clamped = Math.max(Math.min(cMin ?? -Infinity, maxScroll), Math.min(scrollTop, Math.min(cMax ?? Infinity, maxScroll)));
|
|
745
816
|
node.scrollTop = scrollTop;
|
|
746
817
|
// Clamp hitting top/bottom consumes any remainder. Set drainPending
|
|
747
818
|
// only after clamp so a wasted no-op frame isn't scheduled.
|
|
@@ -749,6 +820,51 @@ function renderNodeToOutput(node, output, options) {
|
|
|
749
820
|
node.pendingScrollDelta = undefined;
|
|
750
821
|
if (node.pendingScrollDelta !== undefined)
|
|
751
822
|
scrollDrainNode = node;
|
|
823
|
+
// Geometry forensics (#421/#433): everything that decides where this
|
|
824
|
+
// viewport painted — captured AFTER all clamps so the trace shows the
|
|
825
|
+
// final renderScrollTop, not the requested one.
|
|
826
|
+
if (GEOMETRY_TRACE_ENABLED) {
|
|
827
|
+
noteScrollGeometry({
|
|
828
|
+
sticky,
|
|
829
|
+
shrunk,
|
|
830
|
+
grew,
|
|
831
|
+
atBottom,
|
|
832
|
+
scrollTopBeforeFollow,
|
|
833
|
+
cur,
|
|
834
|
+
scrollTop,
|
|
835
|
+
renderScrollTop: clamped,
|
|
836
|
+
scrollHeight,
|
|
837
|
+
prevScrollHeight,
|
|
838
|
+
innerHeight,
|
|
839
|
+
maxScroll,
|
|
840
|
+
prevMaxScroll,
|
|
841
|
+
clampMin: cMin ?? null,
|
|
842
|
+
clampMax: cMax ?? null,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
// Wheel-drain selection translate (#438): the drain moved content
|
|
846
|
+
// by (scrollTop - scrollTopBeforeFollow) rows this frame, minus
|
|
847
|
+
// what at-bottom follow already reported above (followDelta is 0
|
|
848
|
+
// unless the follow branch fired — and when it did, it cleared
|
|
849
|
+
// pendingScrollDelta, so the drain contributed nothing). Record
|
|
850
|
+
// the remainder as a follow-scroll event with a SIGNED delta so
|
|
851
|
+
// ink.tsx re-anchors any active selection to the text. Without
|
|
852
|
+
// this, wheel scrolling leaves the highlight pinned to screen
|
|
853
|
+
// rows and copy-on-select grabs whatever scrolled under it.
|
|
854
|
+
// scrollTo/scrollToElement jumps never contribute: they write
|
|
855
|
+
// scrollTop before scrollTopBeforeFollow is captured. Multi-frame
|
|
856
|
+
// drains record per-frame portions; the selection's virtual-row
|
|
857
|
+
// tracking accumulates the clamp overshoot across frames. Multiple
|
|
858
|
+
// boxes may each record; ink.tsx attributes by viewport containment.
|
|
859
|
+
const wheelDelta = scrollTop - scrollTopBeforeFollow - followDelta;
|
|
860
|
+
if (wheelDelta !== 0) {
|
|
861
|
+
const wheelVpTop = node.scrollViewportTop ?? 0;
|
|
862
|
+
followScrolls.push({
|
|
863
|
+
delta: wheelDelta,
|
|
864
|
+
viewportTop: wheelVpTop,
|
|
865
|
+
viewportBottom: wheelVpTop + innerHeight - 1
|
|
866
|
+
});
|
|
867
|
+
}
|
|
752
868
|
// A manual scroll that lands exactly on the bottom re-pins sticky
|
|
753
869
|
// IMMEDIATELY on this frame — the follow-block restore above only
|
|
754
870
|
// fires when a later frame happens, but an idle stream (turn done,
|
|
@@ -1066,11 +1182,12 @@ function renderNodeToOutput(node, output, options) {
|
|
|
1066
1182
|
// backgroundColor and opaque both disable child blit: the fill
|
|
1067
1183
|
// overwrites the entire interior each render, so any child whose
|
|
1068
1184
|
// layout position shifted would blit stale cells from prevScreen
|
|
1069
|
-
// on top of the fresh fill.
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
|
|
1185
|
+
// on top of the fresh fill. bgChanged (effective background moved,
|
|
1186
|
+
// e.g. a hover highlight removed) must disable child blit for the
|
|
1187
|
+
// same reason: the old fill lives in prevScreen and the children's
|
|
1188
|
+
// blits would resurrect it — the frame then equals prevScreen and
|
|
1189
|
+
// the diff never clears the stale highlight.
|
|
1190
|
+
ownBackgroundColor || node.style.opaque || bgChanged ? undefined : prevScreen, boxBackgroundColor);
|
|
1074
1191
|
}
|
|
1075
1192
|
if (needsClip) {
|
|
1076
1193
|
output.unclip();
|
|
@@ -1084,10 +1201,11 @@ function renderNodeToOutput(node, output, options) {
|
|
|
1084
1201
|
renderChildren(node, output, x, y, hasRemovedChild, prevScreen, inheritedBackgroundColor);
|
|
1085
1202
|
}
|
|
1086
1203
|
// Cache layout bounds for dirty tracking
|
|
1087
|
-
const rect = { x, y, width, height, top: yogaTop };
|
|
1204
|
+
const rect = { x, y, width, height, top: yogaTop, bg: effectiveBg };
|
|
1088
1205
|
nodeCache.set(node, rect);
|
|
1089
1206
|
if (node.style.position === 'absolute') {
|
|
1090
1207
|
absoluteRectsCur.push(rect);
|
|
1208
|
+
absoluteHitList.push({ node, rect });
|
|
1091
1209
|
}
|
|
1092
1210
|
node.dirty = false;
|
|
1093
1211
|
}
|
|
@@ -1201,6 +1319,7 @@ function blitEscapingAbsoluteDescendants(node, output, prevScreen, px, py, pw, p
|
|
|
1201
1319
|
const cached = nodeCache.get(elem);
|
|
1202
1320
|
if (cached) {
|
|
1203
1321
|
absoluteRectsCur.push(cached);
|
|
1322
|
+
absoluteHitList.push({ node: elem, rect: cached });
|
|
1204
1323
|
const cx = Math.floor(cached.x);
|
|
1205
1324
|
const cy = Math.floor(cached.y);
|
|
1206
1325
|
const cw = Math.floor(cached.width);
|
|
@@ -213,10 +213,12 @@ export declare function shiftAnchor(s: SelectionState, dRow: number, minRow: num
|
|
|
213
213
|
* the selection is text-anchored at both ends — both must move. The
|
|
214
214
|
* isDragging check in ink.tsx picks which shift to apply.
|
|
215
215
|
*
|
|
216
|
-
* If both ends would shift strictly BELOW minRow
|
|
217
|
-
* text has scrolled entirely off the
|
|
218
|
-
*
|
|
219
|
-
*
|
|
216
|
+
* If both ends would shift strictly BELOW minRow or strictly ABOVE maxRow
|
|
217
|
+
* (unclamped), the selected text has scrolled entirely off the viewport —
|
|
218
|
+
* off the top via streaming follow / wheel-down, off the bottom via
|
|
219
|
+
* wheel-up. Clear it — otherwise a single inverted cell lingers at the
|
|
220
|
+
* edge as a ghost (native terminals drop the selection when it leaves
|
|
221
|
+
* scrollback). Landing AT the edge row is
|
|
220
222
|
* still valid: that cell holds the correct text. Returns true if the
|
|
221
223
|
* selection was cleared so the caller can notify React-land subscribers
|
|
222
224
|
* (useHasSelection) — the caller is inside onRender so it can't use
|
|
@@ -229,6 +231,29 @@ export declare function shiftAnchor(s: SelectionState, dRow: number, minRow: num
|
|
|
229
231
|
* entirely off the top, false otherwise.
|
|
230
232
|
*/
|
|
231
233
|
export declare function shiftSelectionForFollow(s: SelectionState, dRow: number, minRow: number, maxRow: number): boolean;
|
|
234
|
+
/** A scroll event reported by a ScrollBox this frame (follow or wheel
|
|
235
|
+
* drain): signed delta plus the box's viewport bounds. Structurally
|
|
236
|
+
* identical to FollowScroll in render-node-to-output. */
|
|
237
|
+
export type ScrollEvent = {
|
|
238
|
+
delta: number;
|
|
239
|
+
viewportTop: number;
|
|
240
|
+
viewportBottom: number;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Pick which scroll event a selection belongs to when several ScrollBoxes
|
|
244
|
+
* scrolled in the same frame (e.g., the transcript still draining a wheel
|
|
245
|
+
* burst while an overlay panel's box scrolls). The selection follows the
|
|
246
|
+
* INNERMOST viewport containing the anchor: overlay panels render on top
|
|
247
|
+
* of the transcript, so a selection inside the overlap rows belongs to
|
|
248
|
+
* the panel, not the covered transcript beneath it. A selection outside
|
|
249
|
+
* every viewport (footer/prompt, static text) follows nothing — scrolling
|
|
250
|
+
* doesn't move the content under it.
|
|
251
|
+
* @param events - this frame's scroll events (signed deltas).
|
|
252
|
+
* @param anchorRow - the selection anchor's screen row, or null when no
|
|
253
|
+
* selection is active.
|
|
254
|
+
* @returns the event the selection should be translated by, or null.
|
|
255
|
+
*/
|
|
256
|
+
export declare function pickFollowForSelection(events: ScrollEvent[], anchorRow: number | null): ScrollEvent | null;
|
|
232
257
|
/**
|
|
233
258
|
* True when a selection is active, meaning both anchor and focus are set.
|
|
234
259
|
* @param s - the selection state to inspect.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/ink/selection.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/ink/selection.ts"],"names":[],"mappings":"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"}
|
|
@@ -579,10 +579,12 @@ export function shiftAnchor(s, dRow, minRow, maxRow) {
|
|
|
579
579
|
* the selection is text-anchored at both ends — both must move. The
|
|
580
580
|
* isDragging check in ink.tsx picks which shift to apply.
|
|
581
581
|
*
|
|
582
|
-
* If both ends would shift strictly BELOW minRow
|
|
583
|
-
* text has scrolled entirely off the
|
|
584
|
-
*
|
|
585
|
-
*
|
|
582
|
+
* If both ends would shift strictly BELOW minRow or strictly ABOVE maxRow
|
|
583
|
+
* (unclamped), the selected text has scrolled entirely off the viewport —
|
|
584
|
+
* off the top via streaming follow / wheel-down, off the bottom via
|
|
585
|
+
* wheel-up. Clear it — otherwise a single inverted cell lingers at the
|
|
586
|
+
* edge as a ghost (native terminals drop the selection when it leaves
|
|
587
|
+
* scrollback). Landing AT the edge row is
|
|
586
588
|
* still valid: that cell holds the correct text. Returns true if the
|
|
587
589
|
* selection was cleared so the caller can notify React-land subscribers
|
|
588
590
|
* (useHasSelection) — the caller is inside onRender so it can't use
|
|
@@ -609,7 +611,11 @@ export function shiftSelectionForFollow(s, dRow, minRow, maxRow) {
|
|
|
609
611
|
const rawFocus = s.focus
|
|
610
612
|
? (s.virtualFocusRow ?? s.focus.row) + dRow
|
|
611
613
|
: undefined;
|
|
612
|
-
|
|
614
|
+
// Both ends strictly past the same edge = selection fully scrolled off
|
|
615
|
+
// (top: follow/wheel-down; bottom: wheel-up). Symmetric clear — a
|
|
616
|
+
// clamped-to-edge pair would render as a 1-row ghost highlight.
|
|
617
|
+
if ((rawAnchor < minRow && rawFocus !== undefined && rawFocus < minRow) ||
|
|
618
|
+
(rawAnchor > maxRow && rawFocus !== undefined && rawFocus > maxRow)) {
|
|
613
619
|
clearSelection(s);
|
|
614
620
|
return true;
|
|
615
621
|
}
|
|
@@ -640,6 +646,36 @@ export function shiftSelectionForFollow(s, dRow, minRow, maxRow) {
|
|
|
640
646
|
}
|
|
641
647
|
return false;
|
|
642
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Pick which scroll event a selection belongs to when several ScrollBoxes
|
|
651
|
+
* scrolled in the same frame (e.g., the transcript still draining a wheel
|
|
652
|
+
* burst while an overlay panel's box scrolls). The selection follows the
|
|
653
|
+
* INNERMOST viewport containing the anchor: overlay panels render on top
|
|
654
|
+
* of the transcript, so a selection inside the overlap rows belongs to
|
|
655
|
+
* the panel, not the covered transcript beneath it. A selection outside
|
|
656
|
+
* every viewport (footer/prompt, static text) follows nothing — scrolling
|
|
657
|
+
* doesn't move the content under it.
|
|
658
|
+
* @param events - this frame's scroll events (signed deltas).
|
|
659
|
+
* @param anchorRow - the selection anchor's screen row, or null when no
|
|
660
|
+
* selection is active.
|
|
661
|
+
* @returns the event the selection should be translated by, or null.
|
|
662
|
+
*/
|
|
663
|
+
export function pickFollowForSelection(events, anchorRow) {
|
|
664
|
+
if (anchorRow === null)
|
|
665
|
+
return null;
|
|
666
|
+
let best = null;
|
|
667
|
+
let bestHeight = Infinity;
|
|
668
|
+
for (const e of events) {
|
|
669
|
+
if (anchorRow < e.viewportTop || anchorRow > e.viewportBottom)
|
|
670
|
+
continue;
|
|
671
|
+
const height = e.viewportBottom - e.viewportTop;
|
|
672
|
+
if (best === null || height < bestHeight) {
|
|
673
|
+
best = e;
|
|
674
|
+
bestHeight = height;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return best;
|
|
678
|
+
}
|
|
643
679
|
/**
|
|
644
680
|
* True when a selection is active, meaning both anchor and focus are set.
|
|
645
681
|
* @param s - the selection state to inspect.
|