@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/screen.cjs CHANGED
@@ -29,6 +29,8 @@ 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
+ BAR_THUMB: () => BAR_THUMB,
33
+ BAR_TRACK: () => BAR_TRACK,
32
34
  Box: () => import_ink6.Box,
33
35
  Divider: () => Divider,
34
36
  FooterPresets: () => FooterPresets,
@@ -38,6 +40,7 @@ __export(screen_exports, {
38
40
  ListItem: () => ListItem,
39
41
  MultiColumnListComponent: () => MultiColumnListComponent,
40
42
  MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
43
+ PAGE_SCROLL_KEY_BINDINGS: () => PAGE_SCROLL_KEY_BINDINGS,
41
44
  React: () => import_react6.default,
42
45
  ScreenBody: () => ScreenBody,
43
46
  ScreenContainer: () => ScreenContainer,
@@ -160,7 +163,43 @@ function ScreenFooter({ lines, textStyle }) {
160
163
  // src/screen/list-components.js
161
164
  var import_react2 = null; // Will be set by load();
162
165
  var import_ink2 = null; // Will be set by load();
166
+
167
+ // src/screen/scrollbar.js
168
+ var BAR_THUMB = "#";
169
+ var BAR_TRACK = "|";
170
+ function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
171
+ const view = Math.max(1, Math.floor(viewportRows));
172
+ const total = Math.max(0, Math.floor(totalLines));
173
+ if (total <= view) return null;
174
+ const maxScroll = total - view;
175
+ const thumbSize = Math.max(1, Math.round(view / total * view));
176
+ const travel = Math.max(0, view - thumbSize);
177
+ const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
178
+ const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
179
+ const glyphs = [];
180
+ for (let i = 0; i < view; i++) {
181
+ glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? BAR_THUMB : BAR_TRACK);
182
+ }
183
+ return glyphs;
184
+ }
185
+ var PAGE_SCROLL_KEY_BINDINGS = [
186
+ { key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
187
+ { key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
188
+ { key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
189
+ { key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
190
+ { key: "pageUp", caption: "page", action: "pageUp", order: 0 },
191
+ { key: "pageDown", caption: "page", action: "pageDown", order: 0 }
192
+ ];
193
+
194
+ // src/screen/list-components.js
163
195
  // Removed: intermediate assignment - code will access import variable directly
196
+ function clipNameForScrollbar(name, needsBar) {
197
+ if (!needsBar || name == null) return name;
198
+ const s = String(name);
199
+ if (s.length <= 1) return s;
200
+ if (/\s$/.test(s)) return s.slice(0, -1);
201
+ return s;
202
+ }
164
203
  function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
165
204
  const [, forceUpdate] = (0, import_react2.useState)({});
166
205
  const termWidth = (process.stdout.columns || 80) - 8;
@@ -374,17 +413,42 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
374
413
  });
375
414
  ctx.setAction("scrollUp", () => {
376
415
  const { scrollOffset: currentScrollOffset } = scrollStateRef.current;
377
- const newScrollOffset = Math.max(0, currentScrollOffset - 1);
378
- setScrollOffset(newScrollOffset);
416
+ setScrollOffset(Math.max(0, currentScrollOffset - 1));
379
417
  forceUpdate({});
380
418
  });
381
419
  ctx.setAction("scrollDown", () => {
382
420
  const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
383
421
  const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
384
- const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
385
- setScrollOffset(newScrollOffset);
422
+ setScrollOffset(Math.min(currentMaxScrollOffset, currentScrollOffset + 1));
423
+ forceUpdate({});
424
+ });
425
+ ctx.setAction("pageUp", () => {
426
+ const { maxHeight: vh } = scrollStateRef.current;
427
+ const page = Math.max(1, vh || 1);
428
+ const newIndex = Math.max(0, selectedIndexRef.current - page);
429
+ selectedIndexRef.current = newIndex;
430
+ const list = displayItemsRef.current;
431
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
432
+ setScrollOffset(newIndex);
386
433
  forceUpdate({});
387
434
  });
435
+ ctx.setAction("pageDown", () => {
436
+ const { maxHeight: vh, totalItems } = scrollStateRef.current;
437
+ const page = Math.max(1, vh || 1);
438
+ const maxIndex = Math.max(0, totalItems - 1);
439
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + page);
440
+ selectedIndexRef.current = newIndex;
441
+ const list = displayItemsRef.current;
442
+ onSelectionChangeRef.current?.(newIndex, list[newIndex]);
443
+ const maxScroll = Math.max(0, totalItems - page);
444
+ setScrollOffset(Math.min(maxScroll, Math.max(0, newIndex - page + 1)));
445
+ forceUpdate({});
446
+ });
447
+ const navBindings = [
448
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
449
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
450
+ ...PAGE_SCROLL_KEY_BINDINGS
451
+ ];
388
452
  if (sortable) {
389
453
  ctx.setAction("toggleSort", () => {
390
454
  const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
@@ -431,8 +495,7 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
431
495
  }
432
496
  };
433
497
  ctx.setKeyBinding([
434
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
435
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
498
+ ...navBindings,
436
499
  {
437
500
  key: "s",
438
501
  caption: sortCaption,
@@ -442,47 +505,47 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
442
505
  ]);
443
506
  ctx.update();
444
507
  } else {
445
- ctx.setKeyBinding([
446
- { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
447
- { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
448
- ]);
508
+ ctx.setKeyBinding(navBindings);
449
509
  }
450
510
  }, [sortOrder, sortable]);
451
511
  const selectedIndex = selectedIndexRef.current;
512
+ const needsBar = displayItems.length > effectiveMaxHeight;
513
+ const barGlyphs = needsBar ? scrollbarGlyphs(effectiveMaxHeight, displayItems.length, clampedScrollOffset) : null;
514
+ const appendBar = (rowContent, displayIndex) => {
515
+ if (!barGlyphs) return rowContent;
516
+ const glyph = barGlyphs[displayIndex] ?? BAR_TRACK;
517
+ return h2(
518
+ import_ink2.Box,
519
+ { flexDirection: "row" },
520
+ rowContent,
521
+ h2(import_ink2.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
522
+ );
523
+ };
452
524
  const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
453
525
  const isFirstVisible = displayIndex === 0;
454
526
  const isLastVisible = displayIndex === visibleItems.length - 1;
455
- let arrowPrefix = "";
456
- let selectionPrefix = "";
457
- if (isFirstVisible && canScrollUp) {
458
- arrowPrefix = "\u2191 ";
459
- } else if (isLastVisible && canScrollDown) {
460
- arrowPrefix = "\u2193 ";
461
- } else {
462
- arrowPrefix = " ";
463
- }
464
- if (isSelected) {
465
- selectionPrefix = selectionMarker;
466
- } else {
467
- selectionPrefix = " ".repeat(selectionMarker.length);
527
+ let arrowPrefix = " ";
528
+ if (!needsBar) {
529
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
530
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
468
531
  }
532
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
533
+ const label = clipNameForScrollbar(item.name, needsBar);
469
534
  return h2(
470
535
  import_ink2.Box,
471
536
  { flexDirection: "row" },
472
- // Arrow (clickable if functional, not highlighted)
473
- h2(import_ink2.Text, {
474
- key: `arrow-${actualIndex}`,
475
- color: "white"
476
- }, arrowPrefix),
477
- // Selection marker space (always same width, not highlighted)
537
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
478
538
  h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
479
- // Item name (highlighted if selected)
480
- h2(import_ink2.Text, {
481
- key: `name-${actualIndex}`,
482
- color: isSelected ? "black" : "white",
483
- backgroundColor: isSelected ? "cyan" : void 0,
484
- bold: isSelected
485
- }, item.name)
539
+ h2(
540
+ import_ink2.Text,
541
+ {
542
+ key: `name-${actualIndex}`,
543
+ color: isSelected ? "black" : "white",
544
+ backgroundColor: isSelected ? "cyan" : void 0,
545
+ bold: isSelected
546
+ },
547
+ label
548
+ )
486
549
  );
487
550
  };
488
551
  const itemRenderer = renderItem || defaultRenderItem;
@@ -495,42 +558,32 @@ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sor
495
558
  if (renderItem) {
496
559
  const isFirstVisible = displayIndex === 0;
497
560
  const isLastVisible = displayIndex === visibleItems.length - 1;
498
- let arrowPrefix = "";
499
- let selectionPrefix = "";
500
- if (isFirstVisible && canScrollUp) {
501
- arrowPrefix = "\u2191 ";
502
- } else if (isLastVisible && canScrollDown) {
503
- arrowPrefix = "\u2193 ";
504
- } else {
505
- arrowPrefix = " ";
506
- }
507
- if (isSelected) {
508
- selectionPrefix = selectionMarker;
509
- } else {
510
- selectionPrefix = " ".repeat(selectionMarker.length);
561
+ let arrowPrefix = " ";
562
+ if (!needsBar) {
563
+ if (isFirstVisible && canScrollUp) arrowPrefix = "\u2191 ";
564
+ else if (isLastVisible && canScrollDown) arrowPrefix = "\u2193 ";
511
565
  }
566
+ const selectionPrefix = isSelected ? selectionMarker : " ".repeat(selectionMarker.length);
567
+ const clipped = needsBar ? { ...item, name: clipNameForScrollbar(item.name, true) } : item;
568
+ const row = h2(
569
+ import_ink2.Box,
570
+ { flexDirection: "row" },
571
+ h2(import_ink2.Text, { key: `arrow-${actualIndex}`, color: "white" }, arrowPrefix),
572
+ h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
573
+ renderItem(clipped, isSelected, displayIndex)
574
+ );
512
575
  return h2(ScreenRow, {
513
576
  key: `item-${actualIndex}`,
514
- children: h2(
515
- import_ink2.Box,
516
- { flexDirection: "row" },
517
- // Arrow (clickable if functional, not highlighted)
518
- h2(import_ink2.Text, {
519
- key: `arrow-${actualIndex}`,
520
- color: "white"
521
- }, arrowPrefix),
522
- // Selection marker space (always same width, not highlighted)
523
- h2(import_ink2.Text, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
524
- // Custom rendered content
525
- renderItem(item, isSelected, displayIndex)
526
- )
527
- });
528
- } else {
529
- return h2(ScreenRow, {
530
- key: `item-${actualIndex}`,
531
- children: itemRenderer(item, isSelected, displayIndex, actualIndex)
577
+ children: appendBar(row, displayIndex)
532
578
  });
533
579
  }
580
+ return h2(ScreenRow, {
581
+ key: `item-${actualIndex}`,
582
+ children: appendBar(
583
+ itemRenderer(item, isSelected, displayIndex, actualIndex),
584
+ displayIndex
585
+ )
586
+ });
534
587
  })
535
588
  );
536
589
  }
@@ -990,8 +1043,6 @@ function InputField({ prompt, value, onChange: _onChange, onSubmit: _onSubmit })
990
1043
  var import_react5 = null; // Will be set by load();
991
1044
  var import_ink5 = null; // Will be set by load();
992
1045
  // Removed: intermediate assignment - code will access import variable directly
993
- var BAR_THUMB = "#";
994
- var BAR_TRACK = "|";
995
1046
  function wrapTextLines(text, cols) {
996
1047
  const w = Math.max(1, Math.floor(Number(cols) || 1));
997
1048
  const out = [];
@@ -1009,21 +1060,6 @@ function wrapTextLines(text, cols) {
1009
1060
  }
1010
1061
  return out;
1011
1062
  }
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
1063
  function padEndVisible(s, w) {
1028
1064
  const t = String(s ?? "");
1029
1065
  if (t.length >= w) return t.slice(0, w);
@@ -1032,12 +1068,7 @@ function padEndVisible(s, w) {
1032
1068
  var SCROLL_KEYS = [
1033
1069
  { key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
1034
1070
  { 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 }
1071
+ ...PAGE_SCROLL_KEY_BINDINGS
1041
1072
  ];
1042
1073
  function ScrollableText({
1043
1074
  ctx,
@@ -1283,6 +1314,8 @@ if (typeof window === "undefined") {
1283
1314
  }
1284
1315
  // Annotate the CommonJS export names for ESM import in node:
1285
1316
  0 && (module.exports = {
1317
+ BAR_THUMB,
1318
+ BAR_TRACK,
1286
1319
  Box,
1287
1320
  Divider,
1288
1321
  FooterPresets,
@@ -1292,6 +1325,7 @@ if (typeof window === "undefined") {
1292
1325
  ListItem,
1293
1326
  MultiColumnListComponent,
1294
1327
  MultiColumnListWithPreviewComponent,
1328
+ PAGE_SCROLL_KEY_BINDINGS,
1295
1329
  React,
1296
1330
  ScreenBody,
1297
1331
  ScreenContainer,