@nmakarov/cli-toolkit 0.81.0 → 0.84.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.
@@ -634,23 +634,23 @@ function formatKeyBindings(bindings, mode = "long") {
634
634
  })));
635
635
  groups.sort((a, b) => a.order - b.order);
636
636
  const items = [];
637
- groups.forEach((group) => {
637
+ groups.forEach((group, groupIndex) => {
638
638
  const bindingWithCustom = resolvedBindings.find(
639
- (b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
639
+ (b) => group.keys.includes(formatBindingKey(b)) && typeof b.resolvedCaption !== "string"
640
640
  );
641
641
  if (bindingWithCustom && bindingWithCustom.resolvedCaption) {
642
642
  items.push(bindingWithCustom.resolvedCaption);
643
643
  } else {
644
- const keyStr = formatKeys(group.keys);
645
- if (mode === "long") {
646
- items.push(`${keyStr} to ${group.caption}`);
647
- } else {
648
- items.push(keyStr);
649
- }
644
+ items.push(formatFooterHotkey(formatKeys(group.keys), group.caption, mode, `kb-${groupIndex}`));
650
645
  }
651
646
  });
652
647
  return items;
653
648
  }
649
+ function formatFooterHotkey(keyStr, caption = "", mode = "long", reactKey = "hotkey") {
650
+ const keyNode = (0, import_react3.createElement)(import_ink3.Text, { key: `${reactKey}-key`, ...FOOTER_HOTKEY_STYLE }, keyStr);
651
+ if (mode !== "long" || !caption) return keyNode;
652
+ return (0, import_react3.createElement)(import_ink3.Text, { key: reactKey, dimColor: true, color: "white" }, keyNode, ` to ${caption}`);
653
+ }
654
654
  function formatKeys(keys) {
655
655
  const keyMap = {
656
656
  escape: "esc",
@@ -920,7 +920,7 @@ async function showMultiColumnListWithPreviewScreen(config2) {
920
920
  }
921
921
  });
922
922
  }
923
- var import_react3, import_ink3, showMenuScreen, showWordGridScreen;
923
+ var import_react3, import_ink3, FOOTER_HOTKEY_STYLE, showMenuScreen, showWordGridScreen;
924
924
  var init_screens = __esm({
925
925
  "src/screen/screens.js"() {
926
926
  import_react3 = require("react");
@@ -928,6 +928,7 @@ var init_screens = __esm({
928
928
  init_components();
929
929
  init_list_components();
930
930
  init_key_bindings();
931
+ FOOTER_HOTKEY_STYLE = { bold: true, color: "white", dimColor: false };
931
932
  showMenuScreen = showListScreen;
932
933
  showWordGridScreen = showMultiColumnListScreen;
933
934
  }
@@ -1042,7 +1043,44 @@ var init_follow_scroll = __esm({
1042
1043
  }
1043
1044
  });
1044
1045
 
1045
- // src/screen/scrollable-text.js
1046
+ // src/screen/visible-text.js
1047
+ function stripAnsi(text) {
1048
+ return String(text ?? "").replace(ANSI_RE, "");
1049
+ }
1050
+ function visibleWidth(text) {
1051
+ return [...stripAnsi(text)].length;
1052
+ }
1053
+ function sliceVisible(text, cols) {
1054
+ const w = Math.max(0, Math.floor(Number(cols) || 0));
1055
+ const s = String(text ?? "");
1056
+ if (w === 0) return "";
1057
+ let vis = 0;
1058
+ let out = "";
1059
+ let i = 0;
1060
+ while (i < s.length && vis < w) {
1061
+ ANSI_RE.lastIndex = i;
1062
+ const m = ANSI_RE.exec(s);
1063
+ if (m && m.index === i) {
1064
+ out += m[0];
1065
+ i += m[0].length;
1066
+ continue;
1067
+ }
1068
+ const cp = s.codePointAt(i);
1069
+ const ch = String.fromCodePoint(cp);
1070
+ out += ch;
1071
+ i += ch.length;
1072
+ vis += 1;
1073
+ }
1074
+ return out;
1075
+ }
1076
+ function padEndVisible(text, cols) {
1077
+ const w = Math.max(0, Math.floor(Number(cols) || 0));
1078
+ const s = String(text ?? "");
1079
+ const vis = visibleWidth(s);
1080
+ if (vis === w) return s;
1081
+ if (vis > w) return sliceVisible(s, w);
1082
+ return s + " ".repeat(w - vis);
1083
+ }
1046
1084
  function wrapTextLines(text, cols) {
1047
1085
  const w = Math.max(1, Math.floor(Number(cols) || 1));
1048
1086
  const out = [];
@@ -1051,20 +1089,28 @@ function wrapTextLines(text, cols) {
1051
1089
  out.push("");
1052
1090
  continue;
1053
1091
  }
1092
+ if (visibleWidth(raw) <= w) {
1093
+ out.push(raw);
1094
+ continue;
1095
+ }
1054
1096
  let rest = raw;
1055
- while (rest.length > w) {
1056
- out.push(rest.slice(0, w));
1057
- rest = rest.slice(w);
1097
+ while (visibleWidth(rest) > w) {
1098
+ const head = sliceVisible(rest, w);
1099
+ out.push(head);
1100
+ rest = rest.slice(head.length);
1058
1101
  }
1059
1102
  if (rest.length > 0) out.push(rest);
1060
1103
  }
1061
1104
  return out;
1062
1105
  }
1063
- function padEndVisible(s, w) {
1064
- const t = String(s ?? "");
1065
- if (t.length >= w) return t.slice(0, w);
1066
- return t + " ".repeat(w - t.length);
1067
- }
1106
+ var ANSI_RE;
1107
+ var init_visible_text = __esm({
1108
+ "src/screen/visible-text.js"() {
1109
+ ANSI_RE = /\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
1110
+ }
1111
+ });
1112
+
1113
+ // src/screen/scrollable-text.js
1068
1114
  function ScrollableText({
1069
1115
  ctx,
1070
1116
  text = "",
@@ -1132,14 +1178,17 @@ function ScrollableText({
1132
1178
  }, [ctx, bindKeys, followBottom]);
1133
1179
  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" : "");
1134
1180
  const rowNodes = visible.map((line, i) => {
1135
- const body = padEndVisible(line, textWidth);
1136
1181
  const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1137
1182
  const isThumb = glyph === BAR_THUMB;
1138
1183
  return h5(
1139
- import_ink5.Text,
1140
- { key: `L${clamped + i}` },
1141
- body,
1142
- glyph ? h5(import_ink5.Text, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1184
+ import_ink5.Box,
1185
+ { key: `L${clamped + i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
1186
+ h5(
1187
+ import_ink5.Box,
1188
+ { width: textWidth, flexShrink: 0 },
1189
+ h5(import_ink5.Text, { wrap: "truncate" }, padEndVisible(line, textWidth))
1190
+ ),
1191
+ showScrollbar ? h5(import_ink5.Box, { width: 1, flexShrink: 0 }, h5(import_ink5.Text, { color: isThumb ? "cyan" : "gray" }, glyph)) : null
1143
1192
  );
1144
1193
  });
1145
1194
  if (bar && visible.length < viewportRows) {
@@ -1147,10 +1196,10 @@ function ScrollableText({
1147
1196
  const glyph = bar[i] ?? BAR_TRACK;
1148
1197
  rowNodes.push(
1149
1198
  h5(
1150
- import_ink5.Text,
1151
- { key: `pad${i}` },
1152
- padEndVisible("", textWidth),
1153
- h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1199
+ import_ink5.Box,
1200
+ { key: `pad${i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
1201
+ h5(import_ink5.Box, { width: textWidth, flexShrink: 0 }, h5(import_ink5.Text, {}, padEndVisible("", textWidth))),
1202
+ h5(import_ink5.Box, { width: 1, flexShrink: 0 }, h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph))
1154
1203
  )
1155
1204
  );
1156
1205
  }
@@ -1171,7 +1220,9 @@ var init_scrollable_text = __esm({
1171
1220
  init_components();
1172
1221
  init_scrollbar();
1173
1222
  init_follow_scroll();
1223
+ init_visible_text();
1174
1224
  init_scrollbar();
1225
+ init_visible_text();
1175
1226
  h5 = import_react5.createElement;
1176
1227
  SCROLL_KEYS = [
1177
1228
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
@@ -1316,6 +1367,7 @@ __export(screen_exports, {
1316
1367
  BAR_TRACK: () => BAR_TRACK,
1317
1368
  Box: () => import_ink6.Box,
1318
1369
  Divider: () => Divider,
1370
+ FOOTER_HOTKEY_STYLE: () => FOOTER_HOTKEY_STYLE,
1319
1371
  FooterPresets: () => FooterPresets,
1320
1372
  GridCell: () => GridCell,
1321
1373
  InputField: () => InputField,
@@ -1341,6 +1393,7 @@ __export(screen_exports, {
1341
1393
  buildFooter: () => buildFooter,
1342
1394
  clampScroll: () => clampScroll,
1343
1395
  formatBindingKey: () => formatBindingKey,
1396
+ formatFooterHotkey: () => formatFooterHotkey,
1344
1397
  h: () => import_react6.createElement,
1345
1398
  isScrolledToBottom: () => isScrolledToBottom,
1346
1399
  load: () => load,
@@ -1348,6 +1401,7 @@ __export(screen_exports, {
1348
1401
  nextScrollAfterContentChange: () => nextScrollAfterContentChange,
1349
1402
  nextScrollAfterUserMove: () => nextScrollAfterUserMove,
1350
1403
  organizeFooterMessages: () => organizeFooterMessages,
1404
+ padEndVisible: () => padEndVisible,
1351
1405
  scrollbarGlyphs: () => scrollbarGlyphs,
1352
1406
  showListScreen: () => showListScreen,
1353
1407
  showMenuScreen: () => showMenuScreen,
@@ -1355,6 +1409,8 @@ __export(screen_exports, {
1355
1409
  showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
1356
1410
  showScreen: () => showScreen,
1357
1411
  showWordGridScreen: () => showWordGridScreen,
1412
+ sliceVisible: () => sliceVisible,
1413
+ stripAnsi: () => stripAnsi,
1358
1414
  useCallback: () => import_react6.useCallback,
1359
1415
  useEffect: () => import_react6.useEffect,
1360
1416
  useInput: () => import_ink6.useInput,
@@ -1362,6 +1418,7 @@ __export(screen_exports, {
1362
1418
  useMemo: () => import_react6.useMemo,
1363
1419
  useRef: () => import_react6.useRef,
1364
1420
  useState: () => import_react6.useState,
1421
+ visibleWidth: () => visibleWidth,
1365
1422
  wrapTextLines: () => wrapTextLines
1366
1423
  });
1367
1424
  async function load() {
@@ -1383,6 +1440,7 @@ var init_screen = __esm({
1383
1440
  init_components();
1384
1441
  init_ui_elements();
1385
1442
  init_scrollable_text();
1443
+ init_visible_text();
1386
1444
  init_follow_scroll();
1387
1445
  init_scrollbar();
1388
1446
  init_key_bindings();