@xynogen/pix-toolbox 0.1.8 → 0.1.10

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/toolbox.ts +52 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-toolbox",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Pi tool — gated tool toggle UI (/toolbox)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/toolbox.ts CHANGED
@@ -34,7 +34,13 @@ import {
34
34
  type TUI,
35
35
  visibleWidth,
36
36
  } from "@earendil-works/pi-tui";
37
- import { frameLines, modalWidth } from "@xynogen/pix-pretty/modal-frame";
37
+ import {
38
+ frameModal,
39
+ MIN_MODAL_HEIGHT,
40
+ ModalPager,
41
+ modalWidth,
42
+ terminalModalHeight,
43
+ } from "@xynogen/pix-pretty/modal-frame";
38
44
 
39
45
  // ─── Constants ──────────────────────────────────────────────────────────────
40
46
 
@@ -334,9 +340,12 @@ export default function registerToolbox(pi: ExtensionAPI): void {
334
340
  };
335
341
  }): Promise<void> {
336
342
  await ctx.ui.custom<null>(
337
- (tui: TUI, theme: Theme, _kb: KeybindingsManager, done: (r: null) => void) => {
343
+ (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (r: null) => void) => {
338
344
  const accent = "accent";
339
345
  const mute = (s: string) => theme.fg("muted", s);
346
+ const guide = (key: string, action: string) =>
347
+ theme.fg("text", key) + theme.fg("dim", ` ${action}`);
348
+ const guideSep = theme.fg("dim", " · ");
340
349
 
341
350
  type RowState = "active" | "gated";
342
351
  const stateOf = (name: string): RowState => (ops.isActive(name) ? "active" : "gated");
@@ -371,7 +380,7 @@ export default function registerToolbox(pi: ExtensionAPI): void {
371
380
 
372
381
  const list = new SelectList(
373
382
  allItems,
374
- Math.min(allItems.length, 14),
383
+ Math.max(1, allItems.length),
375
384
  {
376
385
  selectedPrefix: (t: string) => theme.fg(accent, t),
377
386
  selectedText: (t: string) => theme.fg(accent, t),
@@ -393,6 +402,7 @@ export default function registerToolbox(pi: ExtensionAPI): void {
393
402
 
394
403
  const search = new Input();
395
404
  let statusText = "";
405
+ const pager = new ModalPager();
396
406
 
397
407
  const refreshLabels = () => {
398
408
  for (const it of internal.items) {
@@ -442,30 +452,56 @@ export default function registerToolbox(pi: ExtensionAPI): void {
442
452
  render(w: number) {
443
453
  const mw = modalWidth(w);
444
454
  const inner = mw - 4; // CHROME = 2 border + 2 padding
445
- const lines: string[] = [
446
- theme.fg(accent, theme.bold("🧰 Toolbox")),
447
- theme.fg("muted", "Search:"),
448
- ...search.render(inner),
449
- ...list.render(inner),
450
- ];
451
- if (statusText) lines.push(statusText);
452
- lines.push(
453
- theme.fg("dim", "↑↓ navigate · e enable · d disable · space toggle · esc close"),
455
+ const footer = statusText ? ["", statusText] : [""];
456
+ footer.push(
457
+ guide("↑↓", "navigate") +
458
+ guideSep +
459
+ guide("←→/PgUp/PgDn", "inspect") +
460
+ guideSep +
461
+ guide("e", "enable") +
462
+ guideSep +
463
+ guide("d", "disable") +
464
+ guideSep +
465
+ guide("space", "toggle") +
466
+ guideSep +
467
+ guide("esc", "close"),
454
468
  );
455
- return frameLines({
469
+ const result = frameModal({
456
470
  width: mw,
457
- lines,
471
+ maxHeight: terminalModalHeight(tui.terminal.rows),
472
+ minHeight: MIN_MODAL_HEIGHT,
473
+ header: [
474
+ theme.fg(accent, theme.bold("🧰 Toolbox")),
475
+ theme.fg("muted", "Search:"),
476
+ ...search.render(inner),
477
+ "",
478
+ ],
479
+ body: list.render(inner),
480
+ selectedBodyRange: pager.selectedRange({
481
+ start: internal.selectedIndex,
482
+ end: internal.selectedIndex + 1,
483
+ }),
484
+ footer,
485
+ bodyOffset: pager.bodyOffset,
458
486
  color: (s) => theme.fg(accent, s),
459
487
  bg: (s) => theme.bg("customMessageBg", s),
488
+ fg: (s) => theme.fg("text", s),
460
489
  });
490
+ pager.sync(result);
491
+ return result.lines;
461
492
  },
462
493
  invalidate() {
463
494
  list.invalidate();
464
495
  search.invalidate();
465
496
  },
466
497
  handleInput(data: string) {
498
+ if (pager.handleInput(data, keybindings, true)) {
499
+ tui.requestRender?.();
500
+ return;
501
+ }
467
502
  if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) {
468
503
  list.handleInput?.(data);
504
+ pager.followSelection();
469
505
  } else if (matchesKey(data, Key.enter) || matchesKey(data, Key.escape)) {
470
506
  done(null);
471
507
  return;
@@ -481,10 +517,12 @@ export default function registerToolbox(pi: ExtensionAPI): void {
481
517
  } else {
482
518
  search.handleInput?.(data);
483
519
  applyFilter(search.getValue?.() ?? "");
520
+ pager.followSelection();
484
521
  }
485
522
  } else {
486
523
  search.handleInput?.(data);
487
524
  applyFilter(search.getValue?.() ?? "");
525
+ pager.followSelection();
488
526
  }
489
527
  }
490
528
  list.invalidate();