@nmakarov/cli-toolkit 0.66.0 → 0.68.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/screen.cjs CHANGED
@@ -29,7 +29,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/screen/index.js
30
30
  var screen_exports = {};
31
31
  __export(screen_exports, {
32
- Box: () => import_ink5.Box,
32
+ Box: () => import_ink6.Box,
33
33
  Divider: () => Divider,
34
34
  FooterPresets: () => FooterPresets,
35
35
  GridCell: () => GridCell,
@@ -38,39 +38,45 @@ __export(screen_exports, {
38
38
  ListItem: () => ListItem,
39
39
  MultiColumnListComponent: () => MultiColumnListComponent,
40
40
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
41
- React: () => import_react5.default,
41
+ React: () => import_react6.default,
42
42
  ScreenBody: () => ScreenBody,
43
43
  ScreenContainer: () => ScreenContainer,
44
44
  ScreenDivider: () => ScreenDivider,
45
45
  ScreenFooter: () => ScreenFooter,
46
46
  ScreenRow: () => ScreenRow,
47
47
  ScreenTitle: () => ScreenTitle,
48
- Text: () => import_ink5.Text,
48
+ ScrollableText: () => ScrollableText,
49
+ Text: () => import_ink6.Text,
49
50
  TextBlock: () => TextBlock,
51
+ bindingIdentity: () => bindingIdentity,
52
+ bindingMatchesInput: () => bindingMatchesInput,
50
53
  buildBreadcrumb: () => buildBreadcrumb,
51
54
  buildDetailBreadcrumb: () => buildDetailBreadcrumb,
52
55
  buildFooter: () => buildFooter,
53
- h: () => import_react5.createElement,
56
+ formatBindingKey: () => formatBindingKey,
57
+ h: () => import_react6.createElement,
54
58
  load: () => load,
55
- memo: () => import_react5.memo,
59
+ memo: () => import_react6.memo,
56
60
  organizeFooterMessages: () => organizeFooterMessages,
61
+ scrollbarGlyphs: () => scrollbarGlyphs,
57
62
  showListScreen: () => showListScreen,
58
63
  showMenuScreen: () => showMenuScreen,
59
64
  showMultiColumnListScreen: () => showMultiColumnListScreen,
60
65
  showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
61
66
  showScreen: () => showScreen,
62
67
  showWordGridScreen: () => showWordGridScreen,
63
- useCallback: () => import_react5.useCallback,
64
- useEffect: () => import_react5.useEffect,
65
- useInput: () => import_ink5.useInput,
66
- useLayoutEffect: () => import_react5.useLayoutEffect,
67
- useMemo: () => import_react5.useMemo,
68
- useRef: () => import_react5.useRef,
69
- useState: () => import_react5.useState
68
+ useCallback: () => import_react6.useCallback,
69
+ useEffect: () => import_react6.useEffect,
70
+ useInput: () => import_ink6.useInput,
71
+ useLayoutEffect: () => import_react6.useLayoutEffect,
72
+ useMemo: () => import_react6.useMemo,
73
+ useRef: () => import_react6.useRef,
74
+ useState: () => import_react6.useState,
75
+ wrapTextLines: () => wrapTextLines
70
76
  });
71
77
  module.exports = __toCommonJS(screen_exports);
72
- var import_react5 = null; // Will be set by load();
73
- var import_ink5 = null; // Will be set by load();
78
+ var import_react6 = null; // Will be set by load();
79
+ var import_ink6 = null; // Will be set by load();
74
80
 
75
81
  // src/screen/screens.js
76
82
  var import_react3 = null; // Will be set by load();
@@ -529,6 +535,54 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
529
535
  );
530
536
  }
531
537
 
538
+ // src/screen/key-bindings.js
539
+ function bindingIdentity(b) {
540
+ return [
541
+ String(b?.key ?? ""),
542
+ b?.meta ? "m" : "",
543
+ b?.ctrl ? "c" : "",
544
+ b?.shift ? "s" : ""
545
+ ].join("|");
546
+ }
547
+ function bindingMatchesInput(binding, input, key) {
548
+ if (!binding?.key) return false;
549
+ let keyMatches = false;
550
+ if (key?.[binding.key]) {
551
+ keyMatches = true;
552
+ } else if (input === binding.key) {
553
+ keyMatches = true;
554
+ } else if (key?.name === binding.key) {
555
+ keyMatches = true;
556
+ }
557
+ if (!keyMatches) return false;
558
+ const modOk = (flag, pressed) => {
559
+ if (flag === true) return !!pressed;
560
+ if (flag === false) return !pressed;
561
+ return !pressed;
562
+ };
563
+ if (!modOk(binding.meta, key?.meta)) return false;
564
+ if (!modOk(binding.ctrl, key?.ctrl)) return false;
565
+ if (binding.shift !== void 0 && !!key?.shift !== !!binding.shift) return false;
566
+ return true;
567
+ }
568
+ var KEY_LABELS = {
569
+ escape: "esc",
570
+ leftArrow: "\u2190",
571
+ rightArrow: "\u2192",
572
+ upArrow: "\u2191",
573
+ downArrow: "\u2193",
574
+ return: "enter",
575
+ pageUp: "PgUp",
576
+ pageDown: "PgDn"
577
+ };
578
+ function formatBindingKey(binding) {
579
+ let label = KEY_LABELS[binding.key] || binding.key;
580
+ if (binding.shift) label = `\u21E7${label}`;
581
+ if (binding.ctrl) label = `^${label}`;
582
+ if (binding.meta) label = `\u2325${label}`;
583
+ return label;
584
+ }
585
+
532
586
  // src/screen/screens.js
533
587
  function groupKeyBindings(bindings) {
534
588
  const groups = {};
@@ -542,7 +596,7 @@ function groupKeyBindings(bindings) {
542
596
  order: binding.order || 999
543
597
  };
544
598
  }
545
- groups[caption].keys.push(binding.key);
599
+ groups[caption].keys.push(formatBindingKey(binding));
546
600
  });
547
601
  return Object.values(groups);
548
602
  }
@@ -582,12 +636,14 @@ function formatKeyBindings(bindings, mode = "long") {
582
636
  }
583
637
  function formatKeys(keys) {
584
638
  const keyMap = {
585
- "escape": "esc",
586
- "leftArrow": "\u2190",
587
- "rightArrow": "\u2192",
588
- "upArrow": "\u2191",
589
- "downArrow": "\u2193",
590
- "return": "enter"
639
+ escape: "esc",
640
+ leftArrow: "\u2190",
641
+ rightArrow: "\u2192",
642
+ upArrow: "\u2191",
643
+ downArrow: "\u2193",
644
+ return: "enter",
645
+ pageUp: "PgUp",
646
+ pageDown: "PgDn"
591
647
  };
592
648
  return keys.map((k) => keyMap[k] || k).join("/");
593
649
  }
@@ -627,7 +683,10 @@ async function showScreen(config) {
627
683
  setKeyBinding: (bindingOrBindings) => {
628
684
  const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
629
685
  bindingsToSet.forEach((binding) => {
630
- const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
686
+ const id = bindingIdentity(binding);
687
+ const existingIndex = keyBindings.findIndex(
688
+ (b) => bindingIdentity(b) === id
689
+ );
631
690
  if (existingIndex >= 0) {
632
691
  const existing = keyBindings[existingIndex];
633
692
  if (existing.protected) {
@@ -651,7 +710,10 @@ async function showScreen(config) {
651
710
  });
652
711
  },
653
712
  updateKeyBinding: (keyName, updates) => {
654
- const index = keyBindings.findIndex((b) => b.key === keyName);
713
+ const id = updates && (updates.meta != null || updates.ctrl != null || updates.shift != null) ? bindingIdentity({ key: keyName, ...updates }) : null;
714
+ const index = keyBindings.findIndex(
715
+ (b) => id ? bindingIdentity(b) === id : b.key === keyName
716
+ );
655
717
  if (index >= 0) {
656
718
  keyBindings[index] = {
657
719
  ...keyBindings[index],
@@ -659,11 +721,13 @@ async function showScreen(config) {
659
721
  };
660
722
  }
661
723
  },
662
- removeKeyBinding: (keyName) => {
663
- const index = keyBindings.findIndex((b) => b.key === keyName);
724
+ removeKeyBinding: (keyNameOrBinding) => {
725
+ const index = typeof keyNameOrBinding === "object" && keyNameOrBinding ? keyBindings.findIndex(
726
+ (b) => bindingIdentity(b) === bindingIdentity(keyNameOrBinding)
727
+ ) : keyBindings.findIndex((b) => b.key === keyNameOrBinding);
664
728
  if (index >= 0) {
665
729
  if (keyBindings[index].protected) {
666
- console.warn(`Cannot remove protected key: ${keyName}`);
730
+ console.warn(`Cannot remove protected key: ${keyNameOrBinding}`);
667
731
  return;
668
732
  }
669
733
  keyBindings.splice(index, 1);
@@ -704,24 +768,11 @@ async function showScreen(config) {
704
768
  }
705
769
  let matchedBinding = null;
706
770
  for (const binding of keyBindings) {
707
- let keyMatches = false;
708
- if (key[binding.key]) {
709
- keyMatches = true;
710
- } else if (input === binding.key) {
711
- keyMatches = true;
712
- } else if (key?.name === binding.key) {
713
- keyMatches = true;
714
- }
715
- if (keyMatches) {
716
- if (binding.enabled === false) {
717
- continue;
718
- }
719
- if (binding.condition && !binding.condition(context)) {
720
- continue;
721
- }
722
- matchedBinding = binding;
723
- break;
724
- }
771
+ if (binding.enabled === false) continue;
772
+ if (!bindingMatchesInput(binding, input, key)) continue;
773
+ if (binding.condition && !binding.condition(context)) continue;
774
+ matchedBinding = binding;
775
+ break;
725
776
  }
726
777
  if (matchedBinding && actions[matchedBinding.action]) {
727
778
  actions[matchedBinding.action]({
@@ -935,6 +986,152 @@ function InputField({ prompt, value, onChange: _onChange, onSubmit: _onSubmit })
935
986
  );
936
987
  }
937
988
 
989
+ // src/screen/scrollable-text.js
990
+ var import_react5 = null; // Will be set by load();
991
+ var import_ink5 = null; // Will be set by load();
992
+ // Removed: intermediate assignment - code will access import variable directly
993
+ function wrapTextLines(text, cols) {
994
+ const w = Math.max(1, Math.floor(Number(cols) || 1));
995
+ const out = [];
996
+ for (const raw of String(text ?? "").split("\n")) {
997
+ if (raw.length === 0) {
998
+ out.push("");
999
+ continue;
1000
+ }
1001
+ let rest = raw;
1002
+ while (rest.length > w) {
1003
+ out.push(rest.slice(0, w));
1004
+ rest = rest.slice(w);
1005
+ }
1006
+ if (rest.length > 0) out.push(rest);
1007
+ }
1008
+ return out;
1009
+ }
1010
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
1011
+ const view = Math.max(1, Math.floor(viewportRows));
1012
+ const total = Math.max(0, Math.floor(totalLines));
1013
+ if (total <= view) return null;
1014
+ const maxScroll = total - view;
1015
+ const thumbSize = Math.max(1, Math.round(view / total * view));
1016
+ const travel = Math.max(0, view - thumbSize);
1017
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
1018
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
1019
+ const glyphs = [];
1020
+ for (let i = 0; i < view; i++) {
1021
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
1022
+ }
1023
+ return glyphs;
1024
+ }
1025
+ function padEndVisible(s, w) {
1026
+ const t = String(s ?? "");
1027
+ if (t.length >= w) return t.slice(0, w);
1028
+ return t + " ".repeat(w - t.length);
1029
+ }
1030
+ var SCROLL_KEYS = [
1031
+ { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1032
+ { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1033
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1034
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1035
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1036
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1037
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1038
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1039
+ ];
1040
+ function ScrollableText({
1041
+ ctx,
1042
+ text = "",
1043
+ lines: linesProp,
1044
+ maxHeight,
1045
+ wrap = true,
1046
+ showScrollbar = true,
1047
+ showStatus = true,
1048
+ bindKeys = true,
1049
+ header = null
1050
+ }) {
1051
+ const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
1052
+ const [, bump] = (0, import_react5.useState)(0);
1053
+ const termCols = process.stdout.columns || 80;
1054
+ const termRows = process.stdout.rows || 24;
1055
+ const viewportRows = Math.max(
1056
+ 4,
1057
+ maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1058
+ );
1059
+ const provisionalBar = showScrollbar ? 1 : 0;
1060
+ const wrapCols = Math.max(8, termCols - provisionalBar);
1061
+ const allLines = (0, import_react5.useMemo)(() => {
1062
+ if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1063
+ return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
1064
+ }, [linesProp, text, wrap, wrapCols]);
1065
+ const needsBar = showScrollbar && allLines.length > viewportRows;
1066
+ const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
1067
+ const maxScroll = Math.max(0, allLines.length - viewportRows);
1068
+ const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1069
+ const visible = allLines.slice(clamped, clamped + viewportRows);
1070
+ const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
1071
+ (0, import_react5.useEffect)(() => {
1072
+ setScrollTop((s) => Math.min(s, maxScroll));
1073
+ }, [maxScroll]);
1074
+ const maxScrollRef = (0, import_react5.useRef)(maxScroll);
1075
+ const pageSizeRef = (0, import_react5.useRef)(viewportRows);
1076
+ maxScrollRef.current = maxScroll;
1077
+ pageSizeRef.current = viewportRows;
1078
+ (0, import_react5.useEffect)(() => {
1079
+ if (!ctx || !bindKeys) return void 0;
1080
+ ctx.setKeyBinding(SCROLL_KEYS);
1081
+ ctx.setAction("scrollUp", () => {
1082
+ setScrollTop((s) => Math.max(0, s - 1));
1083
+ bump((n) => n + 1);
1084
+ ctx.update?.();
1085
+ });
1086
+ ctx.setAction("scrollDown", () => {
1087
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
1088
+ bump((n) => n + 1);
1089
+ ctx.update?.();
1090
+ });
1091
+ ctx.setAction("pageUp", () => {
1092
+ setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
1093
+ bump((n) => n + 1);
1094
+ ctx.update?.();
1095
+ });
1096
+ ctx.setAction("pageDown", () => {
1097
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
1098
+ bump((n) => n + 1);
1099
+ ctx.update?.();
1100
+ });
1101
+ return void 0;
1102
+ }, [ctx, bindKeys]);
1103
+ 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" : "");
1104
+ const rowNodes = visible.map((line, i) => {
1105
+ const body = padEndVisible(line, textWidth);
1106
+ const glyph = bar ? bar[i] ?? "\u2502" : "";
1107
+ return h5(
1108
+ import_ink5.Box,
1109
+ { key: `L${clamped + i}`, flexDirection: "row" },
1110
+ h5(import_ink5.Text, {}, body),
1111
+ glyph ? h5(import_ink5.Text, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
1112
+ );
1113
+ });
1114
+ if (bar && visible.length < viewportRows) {
1115
+ for (let i = visible.length; i < viewportRows; i++) {
1116
+ rowNodes.push(
1117
+ h5(
1118
+ import_ink5.Box,
1119
+ { key: `pad${i}`, flexDirection: "row" },
1120
+ h5(import_ink5.Text, {}, padEndVisible("", textWidth)),
1121
+ h5(import_ink5.Text, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
1122
+ )
1123
+ );
1124
+ }
1125
+ }
1126
+ return h5(
1127
+ import_ink5.Box,
1128
+ { flexDirection: "column" },
1129
+ header == null ? null : typeof header === "string" ? h5(import_ink5.Text, { color: "gray" }, header) : header,
1130
+ showStatus ? h5(import_ink5.Text, { color: "gray" }, status) : null,
1131
+ ...rowNodes
1132
+ );
1133
+ }
1134
+
938
1135
  // src/screen/utils.js
939
1136
  function buildBreadcrumb(parts) {
940
1137
  if (parts.length === 0) return "";
@@ -1060,18 +1257,20 @@ async function load() {
1060
1257
  if (loadPromise) return loadPromise;
1061
1258
  var reactMod = await import("react");
1062
1259
  __esmCache["react"] = reactMod;
1063
- import_react5 = __toESM(reactMod, 1);
1260
+ import_react6 = __toESM(reactMod, 1);
1064
1261
  import_react3 = reactMod;
1065
1262
  import_react = reactMod;
1066
1263
  import_react2 = __toESM(reactMod, 1);
1067
1264
  import_react4 = reactMod;
1265
+ import_react5 = reactMod;
1068
1266
  var inkMod = await import("ink");
1069
1267
  __esmCache["ink"] = inkMod;
1070
- import_ink5 = inkMod;
1268
+ import_ink6 = inkMod;
1071
1269
  import_ink3 = inkMod;
1072
1270
  import_ink = inkMod;
1073
1271
  import_ink2 = inkMod;
1074
1272
  import_ink4 = inkMod;
1273
+ import_ink5 = inkMod;
1075
1274
  loadPromise = Promise.resolve();
1076
1275
  return loadPromise;
1077
1276
  }
@@ -1097,15 +1296,20 @@ if (typeof window === "undefined") {
1097
1296
  ScreenFooter,
1098
1297
  ScreenRow,
1099
1298
  ScreenTitle,
1299
+ ScrollableText,
1100
1300
  Text,
1101
1301
  TextBlock,
1302
+ bindingIdentity,
1303
+ bindingMatchesInput,
1102
1304
  buildBreadcrumb,
1103
1305
  buildDetailBreadcrumb,
1104
1306
  buildFooter,
1307
+ formatBindingKey,
1105
1308
  h,
1106
1309
  load,
1107
1310
  memo,
1108
1311
  organizeFooterMessages,
1312
+ scrollbarGlyphs,
1109
1313
  showListScreen,
1110
1314
  showMenuScreen,
1111
1315
  showMultiColumnListScreen,
@@ -1118,6 +1322,7 @@ if (typeof window === "undefined") {
1118
1322
  useLayoutEffect,
1119
1323
  useMemo,
1120
1324
  useRef,
1121
- useState
1325
+ useState,
1326
+ wrapTextLines
1122
1327
  });
1123
1328
  //# sourceMappingURL=screen.cjs.map