@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.
@@ -621,23 +621,23 @@ function formatKeyBindings(bindings, mode = "long") {
621
621
  })));
622
622
  groups.sort((a, b) => a.order - b.order);
623
623
  const items = [];
624
- groups.forEach((group) => {
624
+ groups.forEach((group, groupIndex) => {
625
625
  const bindingWithCustom = resolvedBindings.find(
626
- (b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
626
+ (b) => group.keys.includes(formatBindingKey(b)) && typeof b.resolvedCaption !== "string"
627
627
  );
628
628
  if (bindingWithCustom && bindingWithCustom.resolvedCaption) {
629
629
  items.push(bindingWithCustom.resolvedCaption);
630
630
  } else {
631
- const keyStr = formatKeys(group.keys);
632
- if (mode === "long") {
633
- items.push(`${keyStr} to ${group.caption}`);
634
- } else {
635
- items.push(keyStr);
636
- }
631
+ items.push(formatFooterHotkey(formatKeys(group.keys), group.caption, mode, `kb-${groupIndex}`));
637
632
  }
638
633
  });
639
634
  return items;
640
635
  }
636
+ function formatFooterHotkey(keyStr, caption = "", mode = "long", reactKey = "hotkey") {
637
+ const keyNode = h3(Text3, { key: `${reactKey}-key`, ...FOOTER_HOTKEY_STYLE }, keyStr);
638
+ if (mode !== "long" || !caption) return keyNode;
639
+ return h3(Text3, { key: reactKey, dimColor: true, color: "white" }, keyNode, ` to ${caption}`);
640
+ }
641
641
  function formatKeys(keys) {
642
642
  const keyMap = {
643
643
  escape: "esc",
@@ -907,12 +907,13 @@ async function showMultiColumnListWithPreviewScreen(config2) {
907
907
  }
908
908
  });
909
909
  }
910
- var showMenuScreen, showWordGridScreen;
910
+ var FOOTER_HOTKEY_STYLE, showMenuScreen, showWordGridScreen;
911
911
  var init_screens = __esm({
912
912
  "src/screen/screens.js"() {
913
913
  init_components();
914
914
  init_list_components();
915
915
  init_key_bindings();
916
+ FOOTER_HOTKEY_STYLE = { bold: true, color: "white", dimColor: false };
916
917
  showMenuScreen = showListScreen;
917
918
  showWordGridScreen = showMultiColumnListScreen;
918
919
  }
@@ -1026,9 +1027,44 @@ var init_follow_scroll = __esm({
1026
1027
  }
1027
1028
  });
1028
1029
 
1029
- // src/screen/scrollable-text.js
1030
- import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
1031
- import { Box as Box4, Text as Text5 } from "ink";
1030
+ // src/screen/visible-text.js
1031
+ function stripAnsi(text) {
1032
+ return String(text ?? "").replace(ANSI_RE, "");
1033
+ }
1034
+ function visibleWidth(text) {
1035
+ return [...stripAnsi(text)].length;
1036
+ }
1037
+ function sliceVisible(text, cols) {
1038
+ const w = Math.max(0, Math.floor(Number(cols) || 0));
1039
+ const s = String(text ?? "");
1040
+ if (w === 0) return "";
1041
+ let vis = 0;
1042
+ let out = "";
1043
+ let i = 0;
1044
+ while (i < s.length && vis < w) {
1045
+ ANSI_RE.lastIndex = i;
1046
+ const m = ANSI_RE.exec(s);
1047
+ if (m && m.index === i) {
1048
+ out += m[0];
1049
+ i += m[0].length;
1050
+ continue;
1051
+ }
1052
+ const cp = s.codePointAt(i);
1053
+ const ch = String.fromCodePoint(cp);
1054
+ out += ch;
1055
+ i += ch.length;
1056
+ vis += 1;
1057
+ }
1058
+ return out;
1059
+ }
1060
+ function padEndVisible(text, cols) {
1061
+ const w = Math.max(0, Math.floor(Number(cols) || 0));
1062
+ const s = String(text ?? "");
1063
+ const vis = visibleWidth(s);
1064
+ if (vis === w) return s;
1065
+ if (vis > w) return sliceVisible(s, w);
1066
+ return s + " ".repeat(w - vis);
1067
+ }
1032
1068
  function wrapTextLines(text, cols) {
1033
1069
  const w = Math.max(1, Math.floor(Number(cols) || 1));
1034
1070
  const out = [];
@@ -1037,20 +1073,30 @@ function wrapTextLines(text, cols) {
1037
1073
  out.push("");
1038
1074
  continue;
1039
1075
  }
1076
+ if (visibleWidth(raw) <= w) {
1077
+ out.push(raw);
1078
+ continue;
1079
+ }
1040
1080
  let rest = raw;
1041
- while (rest.length > w) {
1042
- out.push(rest.slice(0, w));
1043
- rest = rest.slice(w);
1081
+ while (visibleWidth(rest) > w) {
1082
+ const head = sliceVisible(rest, w);
1083
+ out.push(head);
1084
+ rest = rest.slice(head.length);
1044
1085
  }
1045
1086
  if (rest.length > 0) out.push(rest);
1046
1087
  }
1047
1088
  return out;
1048
1089
  }
1049
- function padEndVisible(s, w) {
1050
- const t = String(s ?? "");
1051
- if (t.length >= w) return t.slice(0, w);
1052
- return t + " ".repeat(w - t.length);
1053
- }
1090
+ var ANSI_RE;
1091
+ var init_visible_text = __esm({
1092
+ "src/screen/visible-text.js"() {
1093
+ ANSI_RE = /\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
1094
+ }
1095
+ });
1096
+
1097
+ // src/screen/scrollable-text.js
1098
+ import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
1099
+ import { Box as Box4, Text as Text5 } from "ink";
1054
1100
  function ScrollableText({
1055
1101
  ctx,
1056
1102
  text = "",
@@ -1118,14 +1164,17 @@ function ScrollableText({
1118
1164
  }, [ctx, bindKeys, followBottom]);
1119
1165
  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" : "");
1120
1166
  const rowNodes = visible.map((line, i) => {
1121
- const body = padEndVisible(line, textWidth);
1122
1167
  const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1123
1168
  const isThumb = glyph === BAR_THUMB;
1124
1169
  return h5(
1125
- Text5,
1126
- { key: `L${clamped + i}` },
1127
- body,
1128
- glyph ? h5(Text5, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1170
+ Box4,
1171
+ { key: `L${clamped + i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
1172
+ h5(
1173
+ Box4,
1174
+ { width: textWidth, flexShrink: 0 },
1175
+ h5(Text5, { wrap: "truncate" }, padEndVisible(line, textWidth))
1176
+ ),
1177
+ showScrollbar ? h5(Box4, { width: 1, flexShrink: 0 }, h5(Text5, { color: isThumb ? "cyan" : "gray" }, glyph)) : null
1129
1178
  );
1130
1179
  });
1131
1180
  if (bar && visible.length < viewportRows) {
@@ -1133,10 +1182,10 @@ function ScrollableText({
1133
1182
  const glyph = bar[i] ?? BAR_TRACK;
1134
1183
  rowNodes.push(
1135
1184
  h5(
1136
- Text5,
1137
- { key: `pad${i}` },
1138
- padEndVisible("", textWidth),
1139
- h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1185
+ Box4,
1186
+ { key: `pad${i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
1187
+ h5(Box4, { width: textWidth, flexShrink: 0 }, h5(Text5, {}, padEndVisible("", textWidth))),
1188
+ h5(Box4, { width: 1, flexShrink: 0 }, h5(Text5, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph))
1140
1189
  )
1141
1190
  );
1142
1191
  }
@@ -1155,7 +1204,9 @@ var init_scrollable_text = __esm({
1155
1204
  init_components();
1156
1205
  init_scrollbar();
1157
1206
  init_follow_scroll();
1207
+ init_visible_text();
1158
1208
  init_scrollbar();
1209
+ init_visible_text();
1159
1210
  h5 = createElement2;
1160
1211
  SCROLL_KEYS = [
1161
1212
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
@@ -1300,6 +1351,7 @@ __export(screen_exports, {
1300
1351
  BAR_TRACK: () => BAR_TRACK,
1301
1352
  Box: () => Box5,
1302
1353
  Divider: () => Divider,
1354
+ FOOTER_HOTKEY_STYLE: () => FOOTER_HOTKEY_STYLE,
1303
1355
  FooterPresets: () => FooterPresets,
1304
1356
  GridCell: () => GridCell,
1305
1357
  InputField: () => InputField,
@@ -1325,6 +1377,7 @@ __export(screen_exports, {
1325
1377
  buildFooter: () => buildFooter,
1326
1378
  clampScroll: () => clampScroll,
1327
1379
  formatBindingKey: () => formatBindingKey,
1380
+ formatFooterHotkey: () => formatFooterHotkey,
1328
1381
  h: () => createElement3,
1329
1382
  isScrolledToBottom: () => isScrolledToBottom,
1330
1383
  load: () => load,
@@ -1332,6 +1385,7 @@ __export(screen_exports, {
1332
1385
  nextScrollAfterContentChange: () => nextScrollAfterContentChange,
1333
1386
  nextScrollAfterUserMove: () => nextScrollAfterUserMove,
1334
1387
  organizeFooterMessages: () => organizeFooterMessages,
1388
+ padEndVisible: () => padEndVisible,
1335
1389
  scrollbarGlyphs: () => scrollbarGlyphs,
1336
1390
  showListScreen: () => showListScreen,
1337
1391
  showMenuScreen: () => showMenuScreen,
@@ -1339,6 +1393,8 @@ __export(screen_exports, {
1339
1393
  showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
1340
1394
  showScreen: () => showScreen,
1341
1395
  showWordGridScreen: () => showWordGridScreen,
1396
+ sliceVisible: () => sliceVisible,
1397
+ stripAnsi: () => stripAnsi,
1342
1398
  useCallback: () => useCallback,
1343
1399
  useEffect: () => useEffect3,
1344
1400
  useInput: () => useInput2,
@@ -1346,6 +1402,7 @@ __export(screen_exports, {
1346
1402
  useMemo: () => useMemo2,
1347
1403
  useRef: () => useRef3,
1348
1404
  useState: () => useState4,
1405
+ visibleWidth: () => visibleWidth,
1349
1406
  wrapTextLines: () => wrapTextLines
1350
1407
  });
1351
1408
  import React2, { useState as useState4, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useMemo as useMemo2, useCallback, memo, createElement as createElement3 } from "react";
@@ -1367,6 +1424,7 @@ var init_screen = __esm({
1367
1424
  init_components();
1368
1425
  init_ui_elements();
1369
1426
  init_scrollable_text();
1427
+ init_visible_text();
1370
1428
  init_follow_scroll();
1371
1429
  init_scrollbar();
1372
1430
  init_key_bindings();