@nmakarov/cli-toolkit 0.80.0 → 0.82.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/cli-runner.cjs +132 -44
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +134 -46
- package/dist/cli-runner.js.map +1 -1
- package/dist/index.cjs +140 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +134 -46
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +132 -44
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +134 -46
- package/dist/init.js.map +1 -1
- package/dist/screen.cjs +128 -44
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +120 -44
- package/dist/screen.js.map +1 -1
- package/package.json +1 -1
package/dist/screen.js
CHANGED
|
@@ -963,7 +963,66 @@ function InputField({ prompt, value, onChange: _onChange, onSubmit: _onSubmit })
|
|
|
963
963
|
// src/screen/scrollable-text.js
|
|
964
964
|
import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
|
|
965
965
|
import { Box as Box4, Text as Text5 } from "ink";
|
|
966
|
-
|
|
966
|
+
|
|
967
|
+
// src/screen/follow-scroll.js
|
|
968
|
+
function clampScroll(scrollTop, maxScroll) {
|
|
969
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
970
|
+
const top = Number(scrollTop) || 0;
|
|
971
|
+
return Math.min(Math.max(0, top), max);
|
|
972
|
+
}
|
|
973
|
+
function isScrolledToBottom(scrollTop, maxScroll) {
|
|
974
|
+
return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
|
|
975
|
+
}
|
|
976
|
+
function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
|
|
977
|
+
const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
|
|
978
|
+
return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
|
|
979
|
+
}
|
|
980
|
+
function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
|
|
981
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
982
|
+
if (following) return { scrollTop: max, following: true };
|
|
983
|
+
const next = clampScroll(scrollTop, max);
|
|
984
|
+
return { scrollTop: next, following: isScrolledToBottom(next, max) };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/screen/visible-text.js
|
|
988
|
+
var ANSI_RE = /\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
989
|
+
function stripAnsi(text) {
|
|
990
|
+
return String(text ?? "").replace(ANSI_RE, "");
|
|
991
|
+
}
|
|
992
|
+
function visibleWidth(text) {
|
|
993
|
+
return [...stripAnsi(text)].length;
|
|
994
|
+
}
|
|
995
|
+
function sliceVisible(text, cols) {
|
|
996
|
+
const w = Math.max(0, Math.floor(Number(cols) || 0));
|
|
997
|
+
const s = String(text ?? "");
|
|
998
|
+
if (w === 0) return "";
|
|
999
|
+
let vis = 0;
|
|
1000
|
+
let out = "";
|
|
1001
|
+
let i = 0;
|
|
1002
|
+
while (i < s.length && vis < w) {
|
|
1003
|
+
ANSI_RE.lastIndex = i;
|
|
1004
|
+
const m = ANSI_RE.exec(s);
|
|
1005
|
+
if (m && m.index === i) {
|
|
1006
|
+
out += m[0];
|
|
1007
|
+
i += m[0].length;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
const cp = s.codePointAt(i);
|
|
1011
|
+
const ch = String.fromCodePoint(cp);
|
|
1012
|
+
out += ch;
|
|
1013
|
+
i += ch.length;
|
|
1014
|
+
vis += 1;
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function padEndVisible(text, cols) {
|
|
1019
|
+
const w = Math.max(0, Math.floor(Number(cols) || 0));
|
|
1020
|
+
const s = String(text ?? "");
|
|
1021
|
+
const vis = visibleWidth(s);
|
|
1022
|
+
if (vis === w) return s;
|
|
1023
|
+
if (vis > w) return sliceVisible(s, w);
|
|
1024
|
+
return s + " ".repeat(w - vis);
|
|
1025
|
+
}
|
|
967
1026
|
function wrapTextLines(text, cols) {
|
|
968
1027
|
const w = Math.max(1, Math.floor(Number(cols) || 1));
|
|
969
1028
|
const out = [];
|
|
@@ -972,20 +1031,23 @@ function wrapTextLines(text, cols) {
|
|
|
972
1031
|
out.push("");
|
|
973
1032
|
continue;
|
|
974
1033
|
}
|
|
1034
|
+
if (visibleWidth(raw) <= w) {
|
|
1035
|
+
out.push(raw);
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
975
1038
|
let rest = raw;
|
|
976
|
-
while (rest
|
|
977
|
-
|
|
978
|
-
|
|
1039
|
+
while (visibleWidth(rest) > w) {
|
|
1040
|
+
const head = sliceVisible(rest, w);
|
|
1041
|
+
out.push(head);
|
|
1042
|
+
rest = rest.slice(head.length);
|
|
979
1043
|
}
|
|
980
1044
|
if (rest.length > 0) out.push(rest);
|
|
981
1045
|
}
|
|
982
1046
|
return out;
|
|
983
1047
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
return t + " ".repeat(w - t.length);
|
|
988
|
-
}
|
|
1048
|
+
|
|
1049
|
+
// src/screen/scrollable-text.js
|
|
1050
|
+
var h5 = createElement2;
|
|
989
1051
|
var SCROLL_KEYS = [
|
|
990
1052
|
{ key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
|
|
991
1053
|
{ key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
|
|
@@ -1000,9 +1062,11 @@ function ScrollableText({
|
|
|
1000
1062
|
showScrollbar = true,
|
|
1001
1063
|
showStatus = true,
|
|
1002
1064
|
bindKeys = true,
|
|
1003
|
-
header = null
|
|
1065
|
+
header = null,
|
|
1066
|
+
followBottom = false
|
|
1004
1067
|
}) {
|
|
1005
1068
|
const [scrollTop, setScrollTop] = useState3(0);
|
|
1069
|
+
const [following, setFollowing] = useState3(() => !!followBottom);
|
|
1006
1070
|
const [, bump] = useState3(0);
|
|
1007
1071
|
const termRows = process.stdout.rows || 24;
|
|
1008
1072
|
const viewportRows = Math.max(
|
|
@@ -1021,48 +1085,52 @@ function ScrollableText({
|
|
|
1021
1085
|
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1022
1086
|
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1023
1087
|
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1024
|
-
useEffect2(() => {
|
|
1025
|
-
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1026
|
-
}, [maxScroll]);
|
|
1027
1088
|
const maxScrollRef = useRef2(maxScroll);
|
|
1028
1089
|
const pageSizeRef = useRef2(viewportRows);
|
|
1090
|
+
const scrollTopRef = useRef2(scrollTop);
|
|
1091
|
+
const followingRef = useRef2(following);
|
|
1029
1092
|
maxScrollRef.current = maxScroll;
|
|
1030
1093
|
pageSizeRef.current = viewportRows;
|
|
1094
|
+
scrollTopRef.current = scrollTop;
|
|
1095
|
+
followingRef.current = following;
|
|
1096
|
+
useEffect2(() => {
|
|
1097
|
+
const next = nextScrollAfterContentChange({
|
|
1098
|
+
following: followBottom && followingRef.current,
|
|
1099
|
+
scrollTop: scrollTopRef.current,
|
|
1100
|
+
maxScroll
|
|
1101
|
+
});
|
|
1102
|
+
if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
|
|
1103
|
+
if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
|
|
1104
|
+
}, [maxScroll, followBottom]);
|
|
1105
|
+
const applyUserScroll = (delta) => {
|
|
1106
|
+
const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
|
|
1107
|
+
setScrollTop(next.scrollTop);
|
|
1108
|
+
if (followBottom) setFollowing(next.following);
|
|
1109
|
+
bump((n) => n + 1);
|
|
1110
|
+
ctx?.update?.();
|
|
1111
|
+
};
|
|
1031
1112
|
useEffect2(() => {
|
|
1032
1113
|
if (!ctx || !bindKeys) return void 0;
|
|
1033
1114
|
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1034
|
-
ctx.setAction("scrollUp", () =>
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
});
|
|
1039
|
-
ctx.setAction("scrollDown", () => {
|
|
1040
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1041
|
-
bump((n) => n + 1);
|
|
1042
|
-
ctx.update?.();
|
|
1043
|
-
});
|
|
1044
|
-
ctx.setAction("pageUp", () => {
|
|
1045
|
-
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1046
|
-
bump((n) => n + 1);
|
|
1047
|
-
ctx.update?.();
|
|
1048
|
-
});
|
|
1049
|
-
ctx.setAction("pageDown", () => {
|
|
1050
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1051
|
-
bump((n) => n + 1);
|
|
1052
|
-
ctx.update?.();
|
|
1053
|
-
});
|
|
1115
|
+
ctx.setAction("scrollUp", () => applyUserScroll(-1));
|
|
1116
|
+
ctx.setAction("scrollDown", () => applyUserScroll(1));
|
|
1117
|
+
ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
|
|
1118
|
+
ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
|
|
1054
1119
|
return void 0;
|
|
1055
|
-
}, [ctx, bindKeys]);
|
|
1056
|
-
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" : "");
|
|
1120
|
+
}, [ctx, bindKeys, followBottom]);
|
|
1121
|
+
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" : "");
|
|
1057
1122
|
const rowNodes = visible.map((line, i) => {
|
|
1058
|
-
const body = padEndVisible(line, textWidth);
|
|
1059
1123
|
const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
|
|
1060
1124
|
const isThumb = glyph === BAR_THUMB;
|
|
1061
1125
|
return h5(
|
|
1062
|
-
|
|
1063
|
-
{ key: `L${clamped + i}
|
|
1064
|
-
|
|
1065
|
-
|
|
1126
|
+
Box4,
|
|
1127
|
+
{ key: `L${clamped + i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
|
|
1128
|
+
h5(
|
|
1129
|
+
Box4,
|
|
1130
|
+
{ width: textWidth, flexShrink: 0 },
|
|
1131
|
+
h5(Text5, { wrap: "truncate" }, padEndVisible(line, textWidth))
|
|
1132
|
+
),
|
|
1133
|
+
showScrollbar ? h5(Box4, { width: 1, flexShrink: 0 }, h5(Text5, { color: isThumb ? "cyan" : "gray" }, glyph)) : null
|
|
1066
1134
|
);
|
|
1067
1135
|
});
|
|
1068
1136
|
if (bar && visible.length < viewportRows) {
|
|
@@ -1070,10 +1138,10 @@ function ScrollableText({
|
|
|
1070
1138
|
const glyph = bar[i] ?? BAR_TRACK;
|
|
1071
1139
|
rowNodes.push(
|
|
1072
1140
|
h5(
|
|
1073
|
-
|
|
1074
|
-
{ key: `pad${i}
|
|
1075
|
-
padEndVisible("", textWidth),
|
|
1076
|
-
h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
|
|
1141
|
+
Box4,
|
|
1142
|
+
{ key: `pad${i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
|
|
1143
|
+
h5(Box4, { width: textWidth, flexShrink: 0 }, h5(Text5, {}, padEndVisible("", textWidth))),
|
|
1144
|
+
h5(Box4, { width: 1, flexShrink: 0 }, h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph))
|
|
1077
1145
|
)
|
|
1078
1146
|
);
|
|
1079
1147
|
}
|
|
@@ -1249,11 +1317,16 @@ export {
|
|
|
1249
1317
|
buildBreadcrumb,
|
|
1250
1318
|
buildDetailBreadcrumb,
|
|
1251
1319
|
buildFooter,
|
|
1320
|
+
clampScroll,
|
|
1252
1321
|
formatBindingKey,
|
|
1253
1322
|
createElement3 as h,
|
|
1323
|
+
isScrolledToBottom,
|
|
1254
1324
|
load,
|
|
1255
1325
|
memo,
|
|
1326
|
+
nextScrollAfterContentChange,
|
|
1327
|
+
nextScrollAfterUserMove,
|
|
1256
1328
|
organizeFooterMessages,
|
|
1329
|
+
padEndVisible,
|
|
1257
1330
|
scrollbarGlyphs,
|
|
1258
1331
|
showListScreen,
|
|
1259
1332
|
showMenuScreen,
|
|
@@ -1261,6 +1334,8 @@ export {
|
|
|
1261
1334
|
showMultiColumnListWithPreviewScreen,
|
|
1262
1335
|
showScreen,
|
|
1263
1336
|
showWordGridScreen,
|
|
1337
|
+
sliceVisible,
|
|
1338
|
+
stripAnsi,
|
|
1264
1339
|
useCallback,
|
|
1265
1340
|
useEffect3 as useEffect,
|
|
1266
1341
|
useInput2 as useInput,
|
|
@@ -1268,6 +1343,7 @@ export {
|
|
|
1268
1343
|
useMemo2 as useMemo,
|
|
1269
1344
|
useRef3 as useRef,
|
|
1270
1345
|
useState4 as useState,
|
|
1346
|
+
visibleWidth,
|
|
1271
1347
|
wrapTextLines
|
|
1272
1348
|
};
|
|
1273
1349
|
//# sourceMappingURL=screen.js.map
|