@nmakarov/cli-toolkit 0.80.0 → 0.81.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/dist/index.cjs CHANGED
@@ -1018,6 +1018,30 @@ var init_ui_elements = __esm({
1018
1018
  }
1019
1019
  });
1020
1020
 
1021
+ // src/screen/follow-scroll.js
1022
+ function clampScroll(scrollTop, maxScroll) {
1023
+ const max = Math.max(0, Number(maxScroll) || 0);
1024
+ const top = Number(scrollTop) || 0;
1025
+ return Math.min(Math.max(0, top), max);
1026
+ }
1027
+ function isScrolledToBottom(scrollTop, maxScroll) {
1028
+ return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
1029
+ }
1030
+ function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
1031
+ const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
1032
+ return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
1033
+ }
1034
+ function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
1035
+ const max = Math.max(0, Number(maxScroll) || 0);
1036
+ if (following) return { scrollTop: max, following: true };
1037
+ const next = clampScroll(scrollTop, max);
1038
+ return { scrollTop: next, following: isScrolledToBottom(next, max) };
1039
+ }
1040
+ var init_follow_scroll = __esm({
1041
+ "src/screen/follow-scroll.js"() {
1042
+ }
1043
+ });
1044
+
1021
1045
  // src/screen/scrollable-text.js
1022
1046
  function wrapTextLines(text, cols) {
1023
1047
  const w = Math.max(1, Math.floor(Number(cols) || 1));
@@ -1050,9 +1074,11 @@ function ScrollableText({
1050
1074
  showScrollbar = true,
1051
1075
  showStatus = true,
1052
1076
  bindKeys = true,
1053
- header = null
1077
+ header = null,
1078
+ followBottom = false
1054
1079
  }) {
1055
1080
  const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
1081
+ const [following, setFollowing] = (0, import_react5.useState)(() => !!followBottom);
1056
1082
  const [, bump] = (0, import_react5.useState)(0);
1057
1083
  const termRows = process.stdout.rows || 24;
1058
1084
  const viewportRows = Math.max(
@@ -1071,39 +1097,40 @@ function ScrollableText({
1071
1097
  const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1072
1098
  const visible = allLines.slice(clamped, clamped + viewportRows);
1073
1099
  const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
1074
- (0, import_react5.useEffect)(() => {
1075
- setScrollTop((s) => Math.min(s, maxScroll));
1076
- }, [maxScroll]);
1077
1100
  const maxScrollRef = (0, import_react5.useRef)(maxScroll);
1078
1101
  const pageSizeRef = (0, import_react5.useRef)(viewportRows);
1102
+ const scrollTopRef = (0, import_react5.useRef)(scrollTop);
1103
+ const followingRef = (0, import_react5.useRef)(following);
1079
1104
  maxScrollRef.current = maxScroll;
1080
1105
  pageSizeRef.current = viewportRows;
1106
+ scrollTopRef.current = scrollTop;
1107
+ followingRef.current = following;
1108
+ (0, import_react5.useEffect)(() => {
1109
+ const next = nextScrollAfterContentChange({
1110
+ following: followBottom && followingRef.current,
1111
+ scrollTop: scrollTopRef.current,
1112
+ maxScroll
1113
+ });
1114
+ if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
1115
+ if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
1116
+ }, [maxScroll, followBottom]);
1117
+ const applyUserScroll = (delta) => {
1118
+ const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
1119
+ setScrollTop(next.scrollTop);
1120
+ if (followBottom) setFollowing(next.following);
1121
+ bump((n) => n + 1);
1122
+ ctx?.update?.();
1123
+ };
1081
1124
  (0, import_react5.useEffect)(() => {
1082
1125
  if (!ctx || !bindKeys) return void 0;
1083
1126
  ctx.setKeyBinding(SCROLL_KEYS);
1084
- ctx.setAction("scrollUp", () => {
1085
- setScrollTop((s) => Math.max(0, s - 1));
1086
- bump((n) => n + 1);
1087
- ctx.update?.();
1088
- });
1089
- ctx.setAction("scrollDown", () => {
1090
- setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
1091
- bump((n) => n + 1);
1092
- ctx.update?.();
1093
- });
1094
- ctx.setAction("pageUp", () => {
1095
- setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
1096
- bump((n) => n + 1);
1097
- ctx.update?.();
1098
- });
1099
- ctx.setAction("pageDown", () => {
1100
- setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
1101
- bump((n) => n + 1);
1102
- ctx.update?.();
1103
- });
1127
+ ctx.setAction("scrollUp", () => applyUserScroll(-1));
1128
+ ctx.setAction("scrollDown", () => applyUserScroll(1));
1129
+ ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
1130
+ ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
1104
1131
  return void 0;
1105
- }, [ctx, bindKeys]);
1106
- const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
1132
+ }, [ctx, bindKeys, followBottom]);
1133
+ const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "") + (followBottom && following ? " \xB7 follow" : followBottom ? " \xB7 follow off" : "");
1107
1134
  const rowNodes = visible.map((line, i) => {
1108
1135
  const body = padEndVisible(line, textWidth);
1109
1136
  const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
@@ -1143,6 +1170,7 @@ var init_scrollable_text = __esm({
1143
1170
  import_ink5 = require("ink");
1144
1171
  init_components();
1145
1172
  init_scrollbar();
1173
+ init_follow_scroll();
1146
1174
  init_scrollbar();
1147
1175
  h5 = import_react5.createElement;
1148
1176
  SCROLL_KEYS = [
@@ -1298,6 +1326,7 @@ var init_screen = __esm({
1298
1326
  init_components();
1299
1327
  init_ui_elements();
1300
1328
  init_scrollable_text();
1329
+ init_follow_scroll();
1301
1330
  init_scrollbar();
1302
1331
  init_key_bindings();
1303
1332
  init_utils();
@@ -1377,6 +1406,7 @@ __export(src_exports, {
1377
1406
  buildDetailBreadcrumb: () => buildDetailBreadcrumb,
1378
1407
  buildFooter: () => buildFooter,
1379
1408
  bumpPatchVersion: () => bumpPatchVersion,
1409
+ clampScroll: () => clampScroll,
1380
1410
  cloneRepo: () => cloneRepo,
1381
1411
  coerceRuntimeValue: () => coerceRuntimeValue,
1382
1412
  controlLaneTaskNames: () => controlLaneTaskNames,
@@ -1414,6 +1444,7 @@ __export(src_exports, {
1414
1444
  ipcFileLogsTableNameForSourceResource: () => ipcFileLogsTableNameForSourceResource,
1415
1445
  isFreshOnline: () => isFreshOnline,
1416
1446
  isPidAlive: () => isPidAlive,
1447
+ isScrolledToBottom: () => isScrolledToBottom,
1417
1448
  joiEdateType: () => joiEdateType,
1418
1449
  joiStringArrayType: () => joiStringArrayType,
1419
1450
  listAliveRunnerHeartbeats: () => listServicesRegistry,
@@ -1427,6 +1458,8 @@ __export(src_exports, {
1427
1458
  memo: () => import_react6.memo,
1428
1459
  mergeAllowedTasksWithServiceTasks: () => mergeAllowedTasksWithServiceTasks,
1429
1460
  mergeRuntimeParamSpecs: () => mergeRuntimeParamSpecs,
1461
+ nextScrollAfterContentChange: () => nextScrollAfterContentChange,
1462
+ nextScrollAfterUserMove: () => nextScrollAfterUserMove,
1430
1463
  nextTimeMatch: () => nextTimeMatch,
1431
1464
  normalizeAllowedTasks: () => normalizeAllowedTasks,
1432
1465
  npmEnv: () => npmEnv,
@@ -10351,6 +10384,7 @@ var TasksManager = class _TasksManager {
10351
10384
  buildDetailBreadcrumb,
10352
10385
  buildFooter,
10353
10386
  bumpPatchVersion,
10387
+ clampScroll,
10354
10388
  cloneRepo,
10355
10389
  coerceRuntimeValue,
10356
10390
  controlLaneTaskNames,
@@ -10388,6 +10422,7 @@ var TasksManager = class _TasksManager {
10388
10422
  ipcFileLogsTableNameForSourceResource,
10389
10423
  isFreshOnline,
10390
10424
  isPidAlive,
10425
+ isScrolledToBottom,
10391
10426
  joiEdateType,
10392
10427
  joiStringArrayType,
10393
10428
  listAliveRunnerHeartbeats,
@@ -10401,6 +10436,8 @@ var TasksManager = class _TasksManager {
10401
10436
  memo,
10402
10437
  mergeAllowedTasksWithServiceTasks,
10403
10438
  mergeRuntimeParamSpecs,
10439
+ nextScrollAfterContentChange,
10440
+ nextScrollAfterUserMove,
10404
10441
  nextTimeMatch,
10405
10442
  normalizeAllowedTasks,
10406
10443
  npmEnv,