dsh-neotui 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/views.js +25 -4
package/package.json
CHANGED
package/src/views.js
CHANGED
|
@@ -863,7 +863,7 @@ export class ChatView extends Widget {
|
|
|
863
863
|
* shift the hit identity between press and release (the 4-line mystery:
|
|
864
864
|
* the rendered frame and the hit test were consistent, but the identity
|
|
865
865
|
* was re-resolved at release against a stream that had moved). */
|
|
866
|
-
#anchorCtx(info) {
|
|
866
|
+
#anchorCtx(info, pressY = null) {
|
|
867
867
|
const lineKey = (m) => (m ? `${m.nodeIdx}:${m.blockIdx ?? "n"}` : null);
|
|
868
868
|
const topKey = lineKey(this.lineMap[this.view.scrollY]) ?? null;
|
|
869
869
|
let topFirst = -1;
|
|
@@ -884,7 +884,8 @@ export class ChatView extends Widget {
|
|
|
884
884
|
};
|
|
885
885
|
const preHeaderIdx = info ? firstNonEmpty(match) : -1;
|
|
886
886
|
const preHeaderRow = preHeaderIdx >= 0 ? preHeaderIdx - this.view.scrollY : null;
|
|
887
|
-
|
|
887
|
+
const pressRow = pressY !== null && pressY !== undefined && pressY >= 0 ? pressY - this.view.scrollY : null;
|
|
888
|
+
return { lineKey, topKey, topFirst, topOffset, match, firstNonEmpty, preHeaderIdx, preHeaderRow, pressY, pressRow };
|
|
888
889
|
}
|
|
889
890
|
|
|
890
891
|
/** Toggle the block/node under a line-mark, then re-anchor the viewport.
|
|
@@ -899,11 +900,29 @@ export class ChatView extends Widget {
|
|
|
899
900
|
}
|
|
900
901
|
const node = this.nodes[info.nodeIdx];
|
|
901
902
|
if (!node) return false;
|
|
902
|
-
const { lineKey, topKey, topFirst, topOffset, match, firstNonEmpty, preHeaderIdx, preHeaderRow } = ctx ?? this.#anchorCtx(info);
|
|
903
|
+
const { lineKey, topKey, topFirst, topOffset, match, firstNonEmpty, preHeaderIdx, preHeaderRow, pressY, pressRow } = ctx ?? this.#anchorCtx(info);
|
|
904
|
+
let collapsing = false;
|
|
903
905
|
if (process.env.DSH_TUI_DEBUG_CLICK) {
|
|
904
906
|
this.#clickLog(`toggle mark=${JSON.stringify(info)} kind=${node.kind} blockIdx=${info.blockIdx} preHeaderRow=${preHeaderRow} topKey=${topKey} topOffset=${topOffset}`);
|
|
905
907
|
}
|
|
906
908
|
const reanchor = () => {
|
|
909
|
+
// COLLAPSING a block by clicking one of its CONTENT lines: the clicked
|
|
910
|
+
// text itself vanishes, and the content below slides up by the fold
|
|
911
|
+
// size — the "4-line offset" the user sees. Land the fold's LAST line
|
|
912
|
+
// (the trailer/summary) exactly at the clicked row so the eye stays
|
|
913
|
+
// anchored and nothing below visibly shifts.
|
|
914
|
+
if (collapsing && pressY !== null && pressY !== preHeaderIdx && pressRow !== null && pressRow >= 0) {
|
|
915
|
+
let lastIdx = -1;
|
|
916
|
+
for (let i = 0; i < this.lineMap.length; i++) {
|
|
917
|
+
if (match(this.lineMap[i])) lastIdx = i;
|
|
918
|
+
}
|
|
919
|
+
if (lastIdx >= 0) {
|
|
920
|
+
const sy = lastIdx - pressRow;
|
|
921
|
+
this.view.scrollY = Math.max(0, sy);
|
|
922
|
+
if (sy > this.view.maxScroll()) this.view.anchorLock = sy;
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
907
926
|
// primary anchor: the clicked block's header stays at its pre-click
|
|
908
927
|
// viewport row (zero shift) — only when the header was ON-SCREEN.
|
|
909
928
|
// When the fold removed the tail content below, the anchored position
|
|
@@ -946,9 +965,11 @@ export class ChatView extends Widget {
|
|
|
946
965
|
if (b.kind === "reasoning") {
|
|
947
966
|
// clean two-state override: expand ⇄ collapse (never a no-op click)
|
|
948
967
|
const open = this.expanded.has(key) || (!this.collapsedBlocks.has(key) && this.thinkMode === "expanded");
|
|
968
|
+
collapsing = open;
|
|
949
969
|
if (open) { this.expanded.delete(key); this.collapsedBlocks.add(key); }
|
|
950
970
|
else { this.collapsedBlocks.delete(key); this.expanded.add(key); }
|
|
951
971
|
} else {
|
|
972
|
+
collapsing = !this.collapsedBlocks.has(key);
|
|
952
973
|
if (this.collapsedBlocks.has(key)) this.collapsedBlocks.delete(key);
|
|
953
974
|
else this.collapsedBlocks.add(key);
|
|
954
975
|
}
|
|
@@ -1347,7 +1368,7 @@ export class ChatView extends Widget {
|
|
|
1347
1368
|
// between press and release, so resolving the block at release would
|
|
1348
1369
|
// toggle a block a few lines away from the one the user saw.
|
|
1349
1370
|
this.pressInfo = this.lineMap?.[this.pressY] ?? null;
|
|
1350
|
-
this.pressCtx = this.pressInfo ? this.#anchorCtx(this.pressInfo) : null;
|
|
1371
|
+
this.pressCtx = this.pressInfo ? this.#anchorCtx(this.pressInfo, this.pressY) : null;
|
|
1351
1372
|
if (process.env.DSH_TUI_DEBUG_CLICK) {
|
|
1352
1373
|
const t = this.lines[this.pressY]?.map((g) => g.t).join("") ?? "";
|
|
1353
1374
|
this.#clickLog(`press screenY=${ev.y} screenX=${ev.x} lineIdx=${this.pressY} mark=${JSON.stringify(this.pressInfo)} text="${t.slice(0, 40)}" scrollY=${this.view.scrollY} viewY=${this.view.y} viewH=${this.view.h}`);
|