@nmakarov/cli-toolkit 0.69.0 → 0.71.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.cjs CHANGED
@@ -109,7 +109,46 @@ var init_components = __esm({
109
109
  }
110
110
  });
111
111
 
112
+ // src/screen/scrollbar.js
113
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
114
+ const view = Math.max(1, Math.floor(viewportRows));
115
+ const total = Math.max(0, Math.floor(totalLines));
116
+ if (total <= view) return null;
117
+ const maxScroll = total - view;
118
+ const thumbSize = Math.max(1, Math.round(view / total * view));
119
+ const travel = Math.max(0, view - thumbSize);
120
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
121
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
122
+ const glyphs = [];
123
+ for (let i = 0; i < view; i++) {
124
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
125
+ }
126
+ return glyphs;
127
+ }
128
+ var BAR_THUMB, BAR_TRACK, PAGE_SCROLL_KEY_BINDINGS;
129
+ var init_scrollbar = __esm({
130
+ "src/screen/scrollbar.js"() {
131
+ BAR_THUMB = "#";
132
+ BAR_TRACK = "|";
133
+ PAGE_SCROLL_KEY_BINDINGS = [
134
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
135
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
136
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
137
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
138
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
139
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
140
+ ];
141
+ }
142
+ });
143
+
112
144
  // src/screen/list-components.js
145
+ function clipNameForScrollbar(name, needsBar) {
146
+ if (!needsBar || name == null) return name;
147
+ const s = String(name);
148
+ if (s.length <= 1) return s;
149
+ if (/\s$/.test(s)) return s.slice(0, -1);
150
+ return s;
151
+ }
113
152
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
114
153
  const [, forceUpdate] = (0, import_react2.useState)({});
115
154
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -323,17 +362,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
323
362
  });
324
363
  ctx.setAction("scrollUp", () => {
325
364
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
326
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
327
- setScrollOffset(newScrollOffset);
365
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
328
366
  forceUpdate({});
329
367
  });
330
368
  ctx.setAction("scrollDown", () => {
331
369
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
332
370
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
333
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
334
- setScrollOffset(newScrollOffset);
371
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
372
+ forceUpdate({});
373
+ });
374
+ ctx.setAction("pageUp", () => {
375
+ const { maxHeight: vh } = scrollStateRef.current;
376
+ const page = Math.max(1, vh || 1);
377
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
378
+ selectedIndexRef.current = newIndex;
379
+ const list = displayItemsRef.current;
380
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
381
+ setScrollOffset(newIndex);
335
382
  forceUpdate({});
336
383
  });
384
+ ctx.setAction("pageDown", () => {
385
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
386
+ const page = Math.max(1, vh || 1);
387
+ const maxIndex = Math.max(0, totalItems - 1);
388
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
389
+ selectedIndexRef.current = newIndex;
390
+ const list = displayItemsRef.current;
391
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
392
+ const maxScroll = Math.max(0, totalItems - page);
393
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
394
+ forceUpdate({});
395
+ });
396
+ const navBindings = [
397
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
398
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
399
+ ...PAGE_SCROLL_KEY_BINDINGS
400
+ ];
337
401
  if (sortable) {
338
402
  ctx.setAction("toggleSort", () => {
339
403
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -380,8 +444,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
380
444
  }
381
445
  };
382
446
  ctx.setKeyBinding([
383
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
384
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
447
+ ...navBindings,
385
448
  {
386
449
  key: "s",
387
450
  caption: sortCaption,
@@ -391,47 +454,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
391
454
  ]);
392
455
  ctx.update();
393
456
  } else {
394
- ctx.setKeyBinding([
395
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
396
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
397
- ]);
457
+ ctx.setKeyBinding(navBindings);
398
458
  }
399
459
  }, [sortOrder, sortable]);
400
460
  const selectedIndex = selectedIndexRef.current;
461
+ const needsBar = displayItems.length > effectiveMaxHeight;
462
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
463
+ const appendBar = (rowContent, displayIndex) => {
464
+ if (!barGlyphs) return rowContent;
465
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
466
+ return h2(
467
+ import_ink2.Box,
468
+ { flexDirection: "row" },
469
+ rowContent,
470
+ h2(import_ink2.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
471
+ );
472
+ };
401
473
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
402
474
  const isFirstVisible = displayIndex === 0;
403
475
  const isLastVisible = displayIndex === visibleItems.length - 1;
404
- let arrowPrefix = "";
405
- let selectionPrefix = "";
406
- if (isFirstVisible && canScrollUp) {
407
- arrowPrefix = "\u2191 ";
408
- } else if (isLastVisible && canScrollDown) {
409
- arrowPrefix = "\u2193 ";
410
- } else {
411
- arrowPrefix = " ";
412
- }
413
- if (isSelected) {
414
- selectionPrefix = selectionMarker;
415
- } else {
416
- selectionPrefix = " ".repeat(selectionMarker.length);
476
+ let arrowPrefix = " ";
477
+ if (!needsBar) {
478
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
479
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
417
480
  }
481
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
482
+ const label = clipNameForScrollbar(item.name, needsBar);
418
483
  return h2(
419
484
  import_ink2.Box,
420
485
  { flexDirection: "row" },
421
- // Arrow (clickable if functional, not highlighted)
422
- h2(import_ink2.Text, {
423
- key: `arrow-${actualIndex}`,
424
- color: "white"
425
- }, arrowPrefix),
426
- // Selection marker space (always same width, not highlighted)
486
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
427
487
  h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
428
- // Item name (highlighted if selected)
429
- h2(import_ink2.Text, {
430
- key: `name-${actualIndex}`,
431
- color: isSelected ? "black" : "white",
432
- backgroundColor: isSelected ? "cyan" : void 0,
433
- bold: isSelected
434
- }, item.name)
488
+ h2(
489
+ import_ink2.Text,
490
+ {
491
+ key: `name-${actualIndex}`,
492
+ color: isSelected ? "black" : "white",
493
+ backgroundColor: isSelected ? "cyan" : void 0,
494
+ bold: isSelected
495
+ },
496
+ label
497
+ )
435
498
  );
436
499
  };
437
500
  const itemRenderer = renderItem || defaultRenderItem;
@@ -444,42 +507,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
444
507
  if (renderItem) {
445
508
  const isFirstVisible = displayIndex === 0;
446
509
  const isLastVisible = displayIndex === visibleItems.length - 1;
447
- let arrowPrefix = "";
448
- let selectionPrefix = "";
449
- if (isFirstVisible && canScrollUp) {
450
- arrowPrefix = "\u2191 ";
451
- } else if (isLastVisible && canScrollDown) {
452
- arrowPrefix = "\u2193 ";
453
- } else {
454
- arrowPrefix = " ";
455
- }
456
- if (isSelected) {
457
- selectionPrefix = selectionMarker;
458
- } else {
459
- selectionPrefix = " ".repeat(selectionMarker.length);
510
+ let arrowPrefix = " ";
511
+ if (!needsBar) {
512
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
513
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
460
514
  }
515
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
516
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
517
+ const row = h2(
518
+ import_ink2.Box,
519
+ { flexDirection: "row" },
520
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
521
+ h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
522
+ renderItem(clipped, isSelected, displayIndex)
523
+ );
461
524
  return h2(ScreenRow, {
462
525
  key: `item-${actualIndex}`,
463
- children: h2(
464
- import_ink2.Box,
465
- { flexDirection: "row" },
466
- // Arrow (clickable if functional, not highlighted)
467
- h2(import_ink2.Text, {
468
- key: `arrow-${actualIndex}`,
469
- color: "white"
470
- }, arrowPrefix),
471
- // Selection marker space (always same width, not highlighted)
472
- h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
473
- // Custom rendered content
474
- renderItem(item, isSelected, displayIndex)
475
- )
476
- });
477
- } else {
478
- return h2(ScreenRow, {
479
- key: `item-${actualIndex}`,
480
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
526
+ children: appendBar(row, displayIndex)
481
527
  });
482
528
  }
529
+ return h2(ScreenRow, {
530
+ key: `item-${actualIndex}`,
531
+ children: appendBar(
532
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
533
+ displayIndex
534
+ )
535
+ });
483
536
  })
484
537
  );
485
538
  }
@@ -489,6 +542,7 @@ var init_list_components = __esm({
489
542
  import_react2 = __toESM(require("react"), 1);
490
543
  import_ink2 = require("ink");
491
544
  init_components();
545
+ init_scrollbar();
492
546
  h2 = import_react2.createElement;
493
547
  }
494
548
  });
@@ -982,21 +1036,6 @@ function wrapTextLines(text, cols) {
982
1036
  }
983
1037
  return out;
984
1038
  }
985
- function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
986
- const view = Math.max(1, Math.floor(viewportRows));
987
- const total = Math.max(0, Math.floor(totalLines));
988
- if (total <= view) return null;
989
- const maxScroll = total - view;
990
- const thumbSize = Math.max(1, Math.round(view / total * view));
991
- const travel = Math.max(0, view - thumbSize);
992
- const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
993
- const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
994
- const glyphs = [];
995
- for (let i = 0; i < view; i++) {
996
- glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
997
- }
998
- return glyphs;
999
- }
1000
1039
  function padEndVisible(s, w) {
1001
1040
  const t = String(s ?? "");
1002
1041
  if (t.length >= w) return t.slice(0, w);
@@ -1097,24 +1136,19 @@ function ScrollableText({
1097
1136
  ...rowNodes
1098
1137
  );
1099
1138
  }
1100
- var import_react5, import_ink5, h5, BAR_THUMB, BAR_TRACK, SCROLL_KEYS;
1139
+ var import_react5, import_ink5, h5, SCROLL_KEYS;
1101
1140
  var init_scrollable_text = __esm({
1102
1141
  "src/screen/scrollable-text.js"() {
1103
1142
  import_react5 = require("react");
1104
1143
  import_ink5 = require("ink");
1105
1144
  init_components();
1145
+ init_scrollbar();
1146
+ init_scrollbar();
1106
1147
  h5 = import_react5.createElement;
1107
- BAR_THUMB = "#";
1108
- BAR_TRACK = "|";
1109
1148
  SCROLL_KEYS = [
1110
1149
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1111
1150
  { key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
1112
- { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
1113
- { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
1114
- { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
1115
- { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
1116
- { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
1117
- { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
1151
+ ...PAGE_SCROLL_KEY_BINDINGS
1118
1152
  ];
1119
1153
  }
1120
1154
  });
@@ -1250,6 +1284,8 @@ var init_footer_builder = __esm({
1250
1284
  // src/screen/index.js
1251
1285
  var screen_exports = {};
1252
1286
  __export(screen_exports, {
1287
+ BAR_THUMB: () => BAR_THUMB,
1288
+ BAR_TRACK: () => BAR_TRACK,
1253
1289
  Box: () => import_ink6.Box,
1254
1290
  Divider: () => Divider,
1255
1291
  FooterPresets: () => FooterPresets,
@@ -1259,6 +1295,7 @@ __export(screen_exports, {
1259
1295
  ListItem: () => ListItem,
1260
1296
  MultiColumnListComponent: () => MultiColumnListComponent,
1261
1297
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1298
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
1262
1299
  React: () => import_react6.default,
1263
1300
  ScreenBody: () => ScreenBody,
1264
1301
  ScreenContainer: () => ScreenContainer,
@@ -1314,6 +1351,7 @@ var init_screen = __esm({
1314
1351
  init_components();
1315
1352
  init_ui_elements();
1316
1353
  init_scrollable_text();
1354
+ init_scrollbar();
1317
1355
  init_key_bindings();
1318
1356
  init_utils();
1319
1357
  init_footer_builder();