@xynogen/pix-ask 0.2.1 → 0.2.2

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/frame.ts +72 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-ask",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Pi tool \u2014 structured questionnaire UI (ask_user)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/frame.ts CHANGED
@@ -1,6 +1,73 @@
1
- // Re-export shared modal primitives from pix-pretty.
2
- // pix-ask was the original home of frameLines/modalWidth; moved to pix-pretty
3
- // so gate-overlay and confirm can share the same rounded frame style.
1
+ /**
2
+ * Modal frame primitives inlined from pix-pretty/modal-frame to avoid
3
+ * cross-package subpath imports that break under Node CJS require.
4
+ */
4
5
 
5
- export type { FrameOptions } from "@xynogen/pix-pretty/modal-frame";
6
- export { frameLines, modalWidth } from "@xynogen/pix-pretty/modal-frame";
6
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
7
+
8
+ // ── Constants ─────────────────────────────────────────────────────────────────
9
+
10
+ const MIN_WIDTH = 40;
11
+ const MAX_WIDTH = 96;
12
+ const MARGIN = 4;
13
+ /** 2 border cols + 2 padding spaces */
14
+ const CHROME = 4;
15
+
16
+ // ── Width ─────────────────────────────────────────────────────────────────────
17
+
18
+ /** Clamp terminal width to a sane modal width (40–96 cols). */
19
+ export function modalWidth(termWidth: number): number {
20
+ return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, termWidth - MARGIN));
21
+ }
22
+
23
+ // ── Frame ─────────────────────────────────────────────────────────────────────
24
+
25
+ export interface FrameOptions {
26
+ width: number;
27
+ lines: string[];
28
+ /** Color function for border glyphs — e.g. `(s) => theme.fg("accent", s)` */
29
+ color: (s: string) => string;
30
+ /** Background fill function — e.g. `(s) => theme.bg("customMessageBg", s)` */
31
+ bg?: (s: string) => string;
32
+ /** Optional pre-styled string rendered as the first content row (tab bar etc.) */
33
+ top?: string;
34
+ }
35
+
36
+ /**
37
+ * Render a rounded modal box.
38
+ *
39
+ * Returns an array of full-width ANSI strings:
40
+ * ╭──────────────────╮
41
+ * │ [top row] │ ← only if top is set
42
+ * │ content line 1 │
43
+ * │ content line 2 │
44
+ * ╰──────────────────╯
45
+ */
46
+ export function frameLines(opts: FrameOptions): string[] {
47
+ const { width, lines, color, top } = opts;
48
+ const bg = opts.bg ?? ((s: string) => s);
49
+ const inner = Math.max(1, width - CHROME);
50
+ const dashes = "─".repeat(width - 2);
51
+
52
+ const SENTINEL = "\x00";
53
+ const bgOpen = bg(SENTINEL).split(SENTINEL)[0] ?? "";
54
+ const reassert = (s: string): string =>
55
+ bgOpen
56
+ ? s.replace(/\x1b\[([0-9;]*)m/g, (seq, p: string) =>
57
+ p === "0" || p.split(";").includes("49") ? `${seq}${bgOpen}` : seq,
58
+ )
59
+ : s;
60
+
61
+ const row = (content: string): string => {
62
+ const pad = inner - visibleWidth(content);
63
+ const padded =
64
+ pad > 0 ? content + " ".repeat(pad) : truncateToWidth(content, inner);
65
+ return bg(`${color("│")} ${reassert(padded)} ${color("│")}`);
66
+ };
67
+
68
+ const out: string[] = [bg(color(`╭${dashes}╮`))];
69
+ if (top !== undefined) out.push(row(top));
70
+ for (const line of lines) out.push(row(line));
71
+ out.push(bg(color(`╰${dashes}╯`)));
72
+ return out;
73
+ }