@nmakarov/cli-toolkit 0.67.0 → 0.69.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,155 @@ 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
+ var BAR_THUMB = "#";
994
+ var BAR_TRACK = "|";
995
+ function wrapTextLines(text, cols) {
996
+ const w = Math.max(1, Math.floor(Number(cols) || 1));
997
+ const out = [];
998
+ for (const raw of String(text ?? "").split("\n")) {
999
+ if (raw.length === 0) {
1000
+ out.push("");
1001
+ continue;
1002
+ }
1003
+ let rest = raw;
1004
+ while (rest.length > w) {
1005
+ out.push(rest.slice(0, w));
1006
+ rest = rest.slice(w);
1007
+ }
1008
+ if (rest.length > 0) out.push(rest);
1009
+ }
1010
+ return out;
1011
+ }
1012
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
1013
+ const view = Math.max(1, Math.floor(viewportRows));
1014
+ const total = Math.max(0, Math.floor(totalLines));
1015
+ if (total <= view) return null;
1016
+ const maxScroll = total - view;
1017
+ const thumbSize = Math.max(1, Math.round(view / total * view));
1018
+ const travel = Math.max(0, view - thumbSize);
1019
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
1020
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
1021
+ const glyphs = [];
1022
+ for (let i = 0; i < view; i++) {
1023
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
1024
+ }
1025
+ return glyphs;
1026
+ }
1027
+ function padEndVisible(s, w) {
1028
+ const t = String(s ?? "");
1029
+ if (t.length >= w) return t.slice(0, w);
1030
+ return t + " ".repeat(w - t.length);
1031
+ }
1032
+ var SCROLL_KEYS = [
1033
+ { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1034
+ { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1035
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1036
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1037
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1038
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1039
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1040
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1041
+ ];
1042
+ function ScrollableText({
1043
+ ctx,
1044
+ text = "",
1045
+ lines: linesProp,
1046
+ maxHeight,
1047
+ wrap = true,
1048
+ showScrollbar = true,
1049
+ showStatus = true,
1050
+ bindKeys = true,
1051
+ header = null
1052
+ }) {
1053
+ const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
1054
+ const [, bump] = (0, import_react5.useState)(0);
1055
+ const termRows = process.stdout.rows || 24;
1056
+ const viewportRows = Math.max(
1057
+ 4,
1058
+ maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
1059
+ );
1060
+ const contentCols = Math.max(20, getScreenWidth() - 4);
1061
+ const barCols = showScrollbar ? 1 : 0;
1062
+ const textWidth = Math.max(8, contentCols - barCols);
1063
+ const allLines = (0, import_react5.useMemo)(() => {
1064
+ if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
1065
+ return wrap ? wrapTextLines(text, textWidth) : String(text ?? "").split("\n");
1066
+ }, [linesProp, text, wrap, textWidth]);
1067
+ const needsBar = showScrollbar && allLines.length > viewportRows;
1068
+ const maxScroll = Math.max(0, allLines.length - viewportRows);
1069
+ const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
1070
+ const visible = allLines.slice(clamped, clamped + viewportRows);
1071
+ const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
1072
+ (0, import_react5.useEffect)(() => {
1073
+ setScrollTop((s) => Math.min(s, maxScroll));
1074
+ }, [maxScroll]);
1075
+ const maxScrollRef = (0, import_react5.useRef)(maxScroll);
1076
+ const pageSizeRef = (0, import_react5.useRef)(viewportRows);
1077
+ maxScrollRef.current = maxScroll;
1078
+ pageSizeRef.current = viewportRows;
1079
+ (0, import_react5.useEffect)(() => {
1080
+ if (!ctx || !bindKeys) return void 0;
1081
+ ctx.setKeyBinding(SCROLL_KEYS);
1082
+ ctx.setAction("scrollUp", () => {
1083
+ setScrollTop((s) => Math.max(0, s - 1));
1084
+ bump((n) => n + 1);
1085
+ ctx.update?.();
1086
+ });
1087
+ ctx.setAction("scrollDown", () => {
1088
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
1089
+ bump((n) => n + 1);
1090
+ ctx.update?.();
1091
+ });
1092
+ ctx.setAction("pageUp", () => {
1093
+ setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
1094
+ bump((n) => n + 1);
1095
+ ctx.update?.();
1096
+ });
1097
+ ctx.setAction("pageDown", () => {
1098
+ setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
1099
+ bump((n) => n + 1);
1100
+ ctx.update?.();
1101
+ });
1102
+ return void 0;
1103
+ }, [ctx, bindKeys]);
1104
+ 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" : "");
1105
+ const rowNodes = visible.map((line, i) => {
1106
+ const body = padEndVisible(line, textWidth);
1107
+ const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
1108
+ const isThumb = glyph === BAR_THUMB;
1109
+ return h5(
1110
+ import_ink5.Text,
1111
+ { key: `L${clamped + i}` },
1112
+ body,
1113
+ glyph ? h5(import_ink5.Text, { color: isThumb ? "cyan" : "gray" }, glyph) : null
1114
+ );
1115
+ });
1116
+ if (bar && visible.length < viewportRows) {
1117
+ for (let i = visible.length; i < viewportRows; i++) {
1118
+ const glyph = bar[i] ?? BAR_TRACK;
1119
+ rowNodes.push(
1120
+ h5(
1121
+ import_ink5.Text,
1122
+ { key: `pad${i}` },
1123
+ padEndVisible("", textWidth),
1124
+ h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
1125
+ )
1126
+ );
1127
+ }
1128
+ }
1129
+ return h5(
1130
+ import_ink5.Box,
1131
+ { flexDirection: "column" },
1132
+ header == null ? null : typeof header === "string" ? h5(import_ink5.Text, { color: "gray" }, header) : header,
1133
+ showStatus ? h5(import_ink5.Text, { color: "gray" }, status) : null,
1134
+ ...rowNodes
1135
+ );
1136
+ }
1137
+
938
1138
  // src/screen/utils.js
939
1139
  function buildBreadcrumb(parts) {
940
1140
  if (parts.length === 0) return "";
@@ -1060,18 +1260,20 @@ async function load() {
1060
1260
  if (loadPromise) return loadPromise;
1061
1261
  var reactMod = await import("react");
1062
1262
  __esmCache["react"] = reactMod;
1063
- import_react5 = __toESM(reactMod, 1);
1263
+ import_react6 = __toESM(reactMod, 1);
1064
1264
  import_react3 = reactMod;
1065
1265
  import_react = reactMod;
1066
1266
  import_react2 = __toESM(reactMod, 1);
1067
1267
  import_react4 = reactMod;
1268
+ import_react5 = reactMod;
1068
1269
  var inkMod = await import("ink");
1069
1270
  __esmCache["ink"] = inkMod;
1070
- import_ink5 = inkMod;
1271
+ import_ink6 = inkMod;
1071
1272
  import_ink3 = inkMod;
1072
1273
  import_ink = inkMod;
1073
1274
  import_ink2 = inkMod;
1074
1275
  import_ink4 = inkMod;
1276
+ import_ink5 = inkMod;
1075
1277
  loadPromise = Promise.resolve();
1076
1278
  return loadPromise;
1077
1279
  }
@@ -1097,15 +1299,20 @@ if (typeof window === "undefined") {
1097
1299
  ScreenFooter,
1098
1300
  ScreenRow,
1099
1301
  ScreenTitle,
1302
+ ScrollableText,
1100
1303
  Text,
1101
1304
  TextBlock,
1305
+ bindingIdentity,
1306
+ bindingMatchesInput,
1102
1307
  buildBreadcrumb,
1103
1308
  buildDetailBreadcrumb,
1104
1309
  buildFooter,
1310
+ formatBindingKey,
1105
1311
  h,
1106
1312
  load,
1107
1313
  memo,
1108
1314
  organizeFooterMessages,
1315
+ scrollbarGlyphs,
1109
1316
  showListScreen,
1110
1317
  showMenuScreen,
1111
1318
  showMultiColumnListScreen,
@@ -1118,6 +1325,7 @@ if (typeof window === "undefined") {
1118
1325
  useLayoutEffect,
1119
1326
  useMemo,
1120
1327
  useRef,
1121
- useState
1328
+ useState,
1329
+ wrapTextLines
1122
1330
  });
1123
1331
  //# sourceMappingURL=screen.cjs.map