@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.js CHANGED
@@ -996,6 +996,30 @@ var init_ui_elements = __esm({
996
996
  }
997
997
  });
998
998
 
999
+ // src/screen/follow-scroll.js
1000
+ function clampScroll(scrollTop, maxScroll) {
1001
+ const max = Math.max(0, Number(maxScroll) || 0);
1002
+ const top = Number(scrollTop) || 0;
1003
+ return Math.min(Math.max(0, top), max);
1004
+ }
1005
+ function isScrolledToBottom(scrollTop, maxScroll) {
1006
+ return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
1007
+ }
1008
+ function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
1009
+ const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
1010
+ return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
1011
+ }
1012
+ function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
1013
+ const max = Math.max(0, Number(maxScroll) || 0);
1014
+ if (following) return { scrollTop: max, following: true };
1015
+ const next = clampScroll(scrollTop, max);
1016
+ return { scrollTop: next, following: isScrolledToBottom(next, max) };
1017
+ }
1018
+ var init_follow_scroll = __esm({
1019
+ "src/screen/follow-scroll.js"() {
1020
+ }
1021
+ });
1022
+
999
1023
  // src/screen/scrollable-text.js
1000
1024
  import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
1001
1025
  import { Box as Box4, Text as Text5 } from "ink";
@@ -1030,9 +1054,11 @@ function ScrollableText({
1030
1054
  showScrollbar = true,
1031
1055
  showStatus = true,
1032
1056
  bindKeys = true,
1033
- header = null
1057
+ header = null,
1058
+ followBottom = false
1034
1059
  }) {
1035
1060
  const [scrollTop, setScrollTop] = useState3(0);
1061
+ const [following, setFollowing] = useState3(() => !!followBottom);
1036
1062
  const [, bump] = useState3(0);
1037
1063
  const termRows = process.stdout.rows || 24;
1038
1064
  const viewportRows = Math.max(
@@ -1051,39 +1077,40 @@ function ScrollableText({
1051
1077
  const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1052
1078
  const visible = allLines.slice(clamped, clamped + viewportRows);
1053
1079
  const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
1054
- useEffect2(() => {
1055
- setScrollTop((s) => Math.min(s, maxScroll));
1056
- }, [maxScroll]);
1057
1080
  const maxScrollRef = useRef2(maxScroll);
1058
1081
  const pageSizeRef = useRef2(viewportRows);
1082
+ const scrollTopRef = useRef2(scrollTop);
1083
+ const followingRef = useRef2(following);
1059
1084
  maxScrollRef.current = maxScroll;
1060
1085
  pageSizeRef.current = viewportRows;
1086
+ scrollTopRef.current = scrollTop;
1087
+ followingRef.current = following;
1088
+ useEffect2(() => {
1089
+ const next = nextScrollAfterContentChange({
1090
+ following: followBottom && followingRef.current,
1091
+ scrollTop: scrollTopRef.current,
1092
+ maxScroll
1093
+ });
1094
+ if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
1095
+ if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
1096
+ }, [maxScroll, followBottom]);
1097
+ const applyUserScroll = (delta) => {
1098
+ const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
1099
+ setScrollTop(next.scrollTop);
1100
+ if (followBottom) setFollowing(next.following);
1101
+ bump((n) => n + 1);
1102
+ ctx?.update?.();
1103
+ };
1061
1104
  useEffect2(() => {
1062
1105
  if (!ctx || !bindKeys) return void 0;
1063
1106
  ctx.setKeyBinding(SCROLL_KEYS);
1064
- ctx.setAction("scrollUp", () => {
1065
- setScrollTop((s) => Math.max(0, s - 1));
1066
- bump((n) => n + 1);
1067
- ctx.update?.();
1068
- });
1069
- ctx.setAction("scrollDown", () => {
1070
- setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
1071
- bump((n) => n + 1);
1072
- ctx.update?.();
1073
- });
1074
- ctx.setAction("pageUp", () => {
1075
- setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
1076
- bump((n) => n + 1);
1077
- ctx.update?.();
1078
- });
1079
- ctx.setAction("pageDown", () => {
1080
- setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
1081
- bump((n) => n + 1);
1082
- ctx.update?.();
1083
- });
1107
+ ctx.setAction("scrollUp", () => applyUserScroll(-1));
1108
+ ctx.setAction("scrollDown", () => applyUserScroll(1));
1109
+ ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
1110
+ ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
1084
1111
  return void 0;
1085
- }, [ctx, bindKeys]);
1086
- 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" : "");
1112
+ }, [ctx, bindKeys, followBottom]);
1113
+ 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" : "");
1087
1114
  const rowNodes = visible.map((line, i) => {
1088
1115
  const body = padEndVisible(line, textWidth);
1089
1116
  const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
@@ -1121,6 +1148,7 @@ var init_scrollable_text = __esm({
1121
1148
  "src/screen/scrollable-text.js"() {
1122
1149
  init_components();
1123
1150
  init_scrollbar();
1151
+ init_follow_scroll();
1124
1152
  init_scrollbar();
1125
1153
  h5 = createElement2;
1126
1154
  SCROLL_KEYS = [
@@ -1279,6 +1307,7 @@ var init_screen = __esm({
1279
1307
  init_components();
1280
1308
  init_ui_elements();
1281
1309
  init_scrollable_text();
1310
+ init_follow_scroll();
1282
1311
  init_scrollbar();
1283
1312
  init_key_bindings();
1284
1313
  init_utils();
@@ -10164,6 +10193,7 @@ export {
10164
10193
  buildDetailBreadcrumb,
10165
10194
  buildFooter,
10166
10195
  bumpPatchVersion,
10196
+ clampScroll,
10167
10197
  cloneRepo,
10168
10198
  coerceRuntimeValue,
10169
10199
  controlLaneTaskNames,
@@ -10201,6 +10231,7 @@ export {
10201
10231
  ipcFileLogsTableNameForSourceResource,
10202
10232
  isFreshOnline,
10203
10233
  isPidAlive,
10234
+ isScrolledToBottom,
10204
10235
  joiEdateType,
10205
10236
  joiStringArrayType,
10206
10237
  listServicesRegistry as listAliveRunnerHeartbeats,
@@ -10214,6 +10245,8 @@ export {
10214
10245
  memo,
10215
10246
  mergeAllowedTasksWithServiceTasks,
10216
10247
  mergeRuntimeParamSpecs,
10248
+ nextScrollAfterContentChange,
10249
+ nextScrollAfterUserMove,
10217
10250
  nextTimeMatch,
10218
10251
  normalizeAllowedTasks,
10219
10252
  npmEnv,