@nmakarov/cli-toolkit 0.69.0 → 0.70.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/init.js CHANGED
@@ -93,9 +93,48 @@ var init_components = __esm({
93
93
  }
94
94
  });
95
95
 
96
+ // src/screen/scrollbar.js
97
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
98
+ const view = Math.max(1, Math.floor(viewportRows));
99
+ const total = Math.max(0, Math.floor(totalLines));
100
+ if (total <= view) return null;
101
+ const maxScroll = total - view;
102
+ const thumbSize = Math.max(1, Math.round(view / total * view));
103
+ const travel = Math.max(0, view - thumbSize);
104
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
105
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
106
+ const glyphs = [];
107
+ for (let i = 0; i < view; i++) {
108
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
109
+ }
110
+ return glyphs;
111
+ }
112
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
113
+ var init_scrollbar = __esm({
114
+ "src/screen/scrollbar.js"() {
115
+ BAR_THUMB = "#";
116
+ BAR_TRACK = "|";
117
+ PAGE_SCROLL_KEY_BINDINGS = [
118
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
119
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
120
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
121
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
122
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
123
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
124
+ ];
125
+ }
126
+ });
127
+
96
128
  // src/screen/list-components.js
97
129
  import React, { useState, useEffect, useRef, createElement } from "react";
98
130
  import { Box as Box2, Text as Text2 } from "ink";
131
+ function clipNameForScrollbar(name, needsBar) {
132
+ if (!needsBar || name == null) return name;
133
+ const s = String(name);
134
+ if (s.length <= 1) return s;
135
+ if (/\s$/.test(s)) return s.slice(0, -1);
136
+ return s;
137
+ }
99
138
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
100
139
  const [, forceUpdate] = useState({});
101
140
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -309,17 +348,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
309
348
  });
310
349
  ctx.setAction("scrollUp", () => {
311
350
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
312
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
313
- setScrollOffset(newScrollOffset);
351
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
314
352
  forceUpdate({});
315
353
  });
316
354
  ctx.setAction("scrollDown", () => {
317
355
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
318
356
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
319
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
320
- setScrollOffset(newScrollOffset);
357
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
358
+ forceUpdate({});
359
+ });
360
+ ctx.setAction("pageUp", () => {
361
+ const { maxHeight: vh } = scrollStateRef.current;
362
+ const page = Math.max(1, vh || 1);
363
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
364
+ selectedIndexRef.current = newIndex;
365
+ const list = displayItemsRef.current;
366
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
367
+ setScrollOffset(newIndex);
321
368
  forceUpdate({});
322
369
  });
370
+ ctx.setAction("pageDown", () => {
371
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
372
+ const page = Math.max(1, vh || 1);
373
+ const maxIndex = Math.max(0, totalItems - 1);
374
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
375
+ selectedIndexRef.current = newIndex;
376
+ const list = displayItemsRef.current;
377
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
378
+ const maxScroll = Math.max(0, totalItems - page);
379
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
380
+ forceUpdate({});
381
+ });
382
+ const navBindings = [
383
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
384
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
385
+ ...PAGE_SCROLL_KEY_BINDINGS
386
+ ];
323
387
  if (sortable) {
324
388
  ctx.setAction("toggleSort", () => {
325
389
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -366,8 +430,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
366
430
  }
367
431
  };
368
432
  ctx.setKeyBinding([
369
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
370
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
433
+ ...navBindings,
371
434
  {
372
435
  key: "s",
373
436
  caption: sortCaption,
@@ -377,47 +440,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
377
440
  ]);
378
441
  ctx.update();
379
442
  } else {
380
- ctx.setKeyBinding([
381
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
382
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
383
- ]);
443
+ ctx.setKeyBinding(navBindings);
384
444
  }
385
445
  }, [sortOrder, sortable]);
386
446
  const selectedIndex = selectedIndexRef.current;
447
+ const needsBar = displayItems.length > effectiveMaxHeight;
448
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
449
+ const appendBar = (rowContent, displayIndex) => {
450
+ if (!barGlyphs) return rowContent;
451
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
452
+ return h2(
453
+ Box2,
454
+ { flexDirection: "row" },
455
+ rowContent,
456
+ h2(Text2, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
457
+ );
458
+ };
387
459
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
388
460
  const isFirstVisible = displayIndex === 0;
389
461
  const isLastVisible = displayIndex === visibleItems.length - 1;
390
- let arrowPrefix = "";
391
- let selectionPrefix = "";
392
- if (isFirstVisible && canScrollUp) {
393
- arrowPrefix = "\u2191 ";
394
- } else if (isLastVisible && canScrollDown) {
395
- arrowPrefix = "\u2193 ";
396
- } else {
397
- arrowPrefix = " ";
398
- }
399
- if (isSelected) {
400
- selectionPrefix = selectionMarker;
401
- } else {
402
- selectionPrefix = " ".repeat(selectionMarker.length);
462
+ let arrowPrefix = " ";
463
+ if (!needsBar) {
464
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
465
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
403
466
  }
467
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
468
+ const label = clipNameForScrollbar(item.name, needsBar);
404
469
  return h2(
405
470
  Box2,
406
471
  { flexDirection: "row" },
407
- // Arrow (clickable if functional, not highlighted)
408
- h2(Text2, {
409
- key: `arrow-${actualIndex}`,
410
- color: "white"
411
- }, arrowPrefix),
412
- // Selection marker space (always same width, not highlighted)
472
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
413
473
  h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
414
- // Item name (highlighted if selected)
415
- h2(Text2, {
416
- key: `name-${actualIndex}`,
417
- color: isSelected ? "black" : "white",
418
- backgroundColor: isSelected ? "cyan" : void 0,
419
- bold: isSelected
420
- }, item.name)
474
+ h2(
475
+ Text2,
476
+ {
477
+ key: `name-${actualIndex}`,
478
+ color: isSelected ? "black" : "white",
479
+ backgroundColor: isSelected ? "cyan" : void 0,
480
+ bold: isSelected
481
+ },
482
+ label
483
+ )
421
484
  );
422
485
  };
423
486
  const itemRenderer = renderItem || defaultRenderItem;
@@ -430,42 +493,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
430
493
  if (renderItem) {
431
494
  const isFirstVisible = displayIndex === 0;
432
495
  const isLastVisible = displayIndex === visibleItems.length - 1;
433
- let arrowPrefix = "";
434
- let selectionPrefix = "";
435
- if (isFirstVisible && canScrollUp) {
436
- arrowPrefix = "\u2191 ";
437
- } else if (isLastVisible && canScrollDown) {
438
- arrowPrefix = "\u2193 ";
439
- } else {
440
- arrowPrefix = " ";
441
- }
442
- if (isSelected) {
443
- selectionPrefix = selectionMarker;
444
- } else {
445
- selectionPrefix = " ".repeat(selectionMarker.length);
496
+ let arrowPrefix = " ";
497
+ if (!needsBar) {
498
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
499
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
446
500
  }
501
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
502
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
503
+ const row = h2(
504
+ Box2,
505
+ { flexDirection: "row" },
506
+ h2(Text2, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
507
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
508
+ renderItem(clipped, isSelected, displayIndex)
509
+ );
447
510
  return h2(ScreenRow, {
448
511
  key: `item-${actualIndex}`,
449
- children: h2(
450
- Box2,
451
- { flexDirection: "row" },
452
- // Arrow (clickable if functional, not highlighted)
453
- h2(Text2, {
454
- key: `arrow-${actualIndex}`,
455
- color: "white"
456
- }, arrowPrefix),
457
- // Selection marker space (always same width, not highlighted)
458
- h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
459
- // Custom rendered content
460
- renderItem(item, isSelected, displayIndex)
461
- )
462
- });
463
- } else {
464
- return h2(ScreenRow, {
465
- key: `item-${actualIndex}`,
466
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
512
+ children: appendBar(row, displayIndex)
467
513
  });
468
514
  }
515
+ return h2(ScreenRow, {
516
+ key: `item-${actualIndex}`,
517
+ children: appendBar(
518
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
519
+ displayIndex
520
+ )
521
+ });
469
522
  })
470
523
  );
471
524
  }
@@ -473,6 +526,7 @@ var h2;
473
526
  var init_list_components = __esm({
474
527
  "src/screen/list-components.js"() {
475
528
  init_components();
529
+ init_scrollbar();
476
530
  h2 = createElement;
477
531
  }
478
532
  });
@@ -967,21 +1021,6 @@ function wrapTextLines(text, cols) {
967
1021
  }
968
1022
  return out;
969
1023
  }
970
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
971
- const view = Math.max(1, Math.floor(viewportRows));
972
- const total = Math.max(0, Math.floor(totalLines));
973
- if (total <= view) return null;
974
- const maxScroll = total - view;
975
- const thumbSize = Math.max(1, Math.round(view / total * view));
976
- const travel = Math.max(0, view - thumbSize);
977
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
978
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
979
- const glyphs = [];
980
- for (let i = 0; i < view; i++) {
981
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
982
- }
983
- return glyphs;
984
- }
985
1024
  function padEndVisible(s, w) {
986
1025
  const t = String(s ?? "");
987
1026
  if (t.length >= w) return t.slice(0, w);
@@ -1082,22 +1121,17 @@ function ScrollableText({
1082
1121
  ...rowNodes
1083
1122
  );
1084
1123
  }
1085
- var h5, BAR_THUMB, BAR_TRACK, SCROLL_KEYS;
1124
+ var h5, SCROLL_KEYS;
1086
1125
  var init_scrollable_text = __esm({
1087
1126
  "src/screen/scrollable-text.js"() {
1088
1127
  init_components();
1128
+ init_scrollbar();
1129
+ init_scrollbar();
1089
1130
  h5 = createElement2;
1090
- BAR_THUMB = "#";
1091
- BAR_TRACK = "|";
1092
1131
  SCROLL_KEYS = [
1093
1132
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1094
1133
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1095
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1096
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1097
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1098
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1099
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1100
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1134
+ ...PAGE_SCROLL_KEY_BINDINGS
1101
1135
  ];
1102
1136
  }
1103
1137
  });
@@ -1233,6 +1267,8 @@ var init_footer_builder = __esm({
1233
1267
  // src/screen/index.js
1234
1268
  var screen_exports = {};
1235
1269
  __export(screen_exports, {
1270
+ BAR_THUMB: () => BAR_THUMB,
1271
+ BAR_TRACK: () => BAR_TRACK,
1236
1272
  Box: () => Box5,
1237
1273
  Divider: () => Divider,
1238
1274
  FooterPresets: () => FooterPresets,
@@ -1242,6 +1278,7 @@ __export(screen_exports, {
1242
1278
  ListItem: () => ListItem,
1243
1279
  MultiColumnListComponent: () => MultiColumnListComponent,
1244
1280
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1281
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1245
1282
  React: () => React2,
1246
1283
  ScreenBody: () => ScreenBody,
1247
1284
  ScreenContainer: () => ScreenContainer,
@@ -1297,6 +1334,7 @@ var init_screen = __esm({
1297
1334
  init_components();
1298
1335
  init_ui_elements();
1299
1336
  init_scrollable_text();
1337
+ init_scrollbar();
1300
1338
  init_key_bindings();
1301
1339
  init_utils();
1302
1340
  init_footer_builder();