@teammates/consolonia 0.2.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.
Files changed (104) hide show
  1. package/README.md +48 -0
  2. package/dist/__tests__/ansi.test.d.ts +1 -0
  3. package/dist/__tests__/ansi.test.js +520 -0
  4. package/dist/__tests__/chat-view.test.d.ts +4 -0
  5. package/dist/__tests__/chat-view.test.js +480 -0
  6. package/dist/__tests__/drawing.test.d.ts +4 -0
  7. package/dist/__tests__/drawing.test.js +426 -0
  8. package/dist/__tests__/input.test.d.ts +5 -0
  9. package/dist/__tests__/input.test.js +911 -0
  10. package/dist/__tests__/layout.test.d.ts +4 -0
  11. package/dist/__tests__/layout.test.js +689 -0
  12. package/dist/__tests__/pixel.test.d.ts +1 -0
  13. package/dist/__tests__/pixel.test.js +674 -0
  14. package/dist/__tests__/render.test.d.ts +1 -0
  15. package/dist/__tests__/render.test.js +400 -0
  16. package/dist/__tests__/styled.test.d.ts +4 -0
  17. package/dist/__tests__/styled.test.js +149 -0
  18. package/dist/__tests__/widgets.test.d.ts +5 -0
  19. package/dist/__tests__/widgets.test.js +924 -0
  20. package/dist/ansi/esc.d.ts +61 -0
  21. package/dist/ansi/esc.js +85 -0
  22. package/dist/ansi/output.d.ts +66 -0
  23. package/dist/ansi/output.js +192 -0
  24. package/dist/ansi/strip.d.ts +16 -0
  25. package/dist/ansi/strip.js +74 -0
  26. package/dist/app.d.ts +68 -0
  27. package/dist/app.js +297 -0
  28. package/dist/drawing/clip.d.ts +23 -0
  29. package/dist/drawing/clip.js +67 -0
  30. package/dist/drawing/context.d.ts +77 -0
  31. package/dist/drawing/context.js +275 -0
  32. package/dist/index.d.ts +48 -0
  33. package/dist/index.js +63 -0
  34. package/dist/input/escape-matcher.d.ts +27 -0
  35. package/dist/input/escape-matcher.js +253 -0
  36. package/dist/input/events.d.ts +49 -0
  37. package/dist/input/events.js +17 -0
  38. package/dist/input/index.d.ts +15 -0
  39. package/dist/input/index.js +14 -0
  40. package/dist/input/matcher.d.ts +23 -0
  41. package/dist/input/matcher.js +14 -0
  42. package/dist/input/mouse-matcher.d.ts +27 -0
  43. package/dist/input/mouse-matcher.js +142 -0
  44. package/dist/input/paste-matcher.d.ts +23 -0
  45. package/dist/input/paste-matcher.js +104 -0
  46. package/dist/input/processor.d.ts +51 -0
  47. package/dist/input/processor.js +145 -0
  48. package/dist/input/raw-mode.d.ts +13 -0
  49. package/dist/input/raw-mode.js +24 -0
  50. package/dist/input/text-matcher.d.ts +14 -0
  51. package/dist/input/text-matcher.js +32 -0
  52. package/dist/layout/box.d.ts +33 -0
  53. package/dist/layout/box.js +92 -0
  54. package/dist/layout/column.d.ts +21 -0
  55. package/dist/layout/column.js +90 -0
  56. package/dist/layout/control.d.ts +73 -0
  57. package/dist/layout/control.js +215 -0
  58. package/dist/layout/row.d.ts +21 -0
  59. package/dist/layout/row.js +95 -0
  60. package/dist/layout/stack.d.ts +18 -0
  61. package/dist/layout/stack.js +64 -0
  62. package/dist/layout/types.d.ts +27 -0
  63. package/dist/layout/types.js +4 -0
  64. package/dist/pixel/background.d.ts +16 -0
  65. package/dist/pixel/background.js +16 -0
  66. package/dist/pixel/box-pattern.d.ts +38 -0
  67. package/dist/pixel/box-pattern.js +57 -0
  68. package/dist/pixel/buffer.d.ts +25 -0
  69. package/dist/pixel/buffer.js +51 -0
  70. package/dist/pixel/color.d.ts +48 -0
  71. package/dist/pixel/color.js +92 -0
  72. package/dist/pixel/foreground.d.ts +31 -0
  73. package/dist/pixel/foreground.js +64 -0
  74. package/dist/pixel/pixel.d.ts +21 -0
  75. package/dist/pixel/pixel.js +38 -0
  76. package/dist/pixel/symbol.d.ts +38 -0
  77. package/dist/pixel/symbol.js +192 -0
  78. package/dist/render/regions.d.ts +54 -0
  79. package/dist/render/regions.js +102 -0
  80. package/dist/render/render-target.d.ts +42 -0
  81. package/dist/render/render-target.js +118 -0
  82. package/dist/styled.d.ts +113 -0
  83. package/dist/styled.js +176 -0
  84. package/dist/widgets/border.d.ts +34 -0
  85. package/dist/widgets/border.js +121 -0
  86. package/dist/widgets/chat-view.d.ts +239 -0
  87. package/dist/widgets/chat-view.js +993 -0
  88. package/dist/widgets/interview.d.ts +87 -0
  89. package/dist/widgets/interview.js +187 -0
  90. package/dist/widgets/markdown.d.ts +87 -0
  91. package/dist/widgets/markdown.js +611 -0
  92. package/dist/widgets/panel.d.ts +19 -0
  93. package/dist/widgets/panel.js +35 -0
  94. package/dist/widgets/scroll-view.d.ts +43 -0
  95. package/dist/widgets/scroll-view.js +182 -0
  96. package/dist/widgets/styled-text.d.ts +38 -0
  97. package/dist/widgets/styled-text.js +183 -0
  98. package/dist/widgets/syntax.d.ts +37 -0
  99. package/dist/widgets/syntax.js +670 -0
  100. package/dist/widgets/text-input.d.ts +121 -0
  101. package/dist/widgets/text-input.js +618 -0
  102. package/dist/widgets/text.d.ts +34 -0
  103. package/dist/widgets/text.js +168 -0
  104. package/package.json +45 -0
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Interview — a modal widget that presents a sequence of questions
3
+ * and collects answers. Designed to run inside a ChatView by replacing
4
+ * the normal input area (via ChatView.setInputOverride).
5
+ *
6
+ * While active the ChatView's own input prompt is hidden; when the
7
+ * interview completes it emits "complete" with all collected answers
8
+ * and the caller can remove the override to restore normal input.
9
+ *
10
+ * Usage:
11
+ *
12
+ * const interview = new Interview({
13
+ * title: "Quick intro",
14
+ * subtitle: "press Enter to skip any question",
15
+ * questions: [
16
+ * { key: "name", prompt: "Your name" },
17
+ * { key: "role", prompt: "Your role", placeholder: "e.g., senior backend engineer" },
18
+ * ],
19
+ * });
20
+ *
21
+ * chatView.setInputOverride(interview);
22
+ *
23
+ * interview.on("complete", (answers: Record<string, string>) => {
24
+ * chatView.setInputOverride(null); // restore normal input
25
+ * // use answers...
26
+ * });
27
+ */
28
+ import type { DrawingContext, TextStyle } from "../drawing/context.js";
29
+ import type { InputEvent } from "../input/events.js";
30
+ import { Control } from "../layout/control.js";
31
+ import type { Constraint, Size } from "../layout/types.js";
32
+ export interface InterviewQuestion {
33
+ /** Key used in the answers record. */
34
+ key: string;
35
+ /** Prompt label shown to the left of the input. */
36
+ prompt: string;
37
+ /** Placeholder hint shown when the input is empty. */
38
+ placeholder?: string;
39
+ }
40
+ export interface InterviewOptions {
41
+ /** Title shown above the questions. */
42
+ title?: string;
43
+ /** Subtitle / hint shown below the title. */
44
+ subtitle?: string;
45
+ /** The questions to ask, in order. */
46
+ questions: InterviewQuestion[];
47
+ /** Style for the question prompt label. */
48
+ promptStyle?: TextStyle;
49
+ /** Style for answered values. */
50
+ answeredStyle?: TextStyle;
51
+ /** Style for input text. */
52
+ inputStyle?: TextStyle;
53
+ /** Style for the cursor. */
54
+ cursorStyle?: TextStyle;
55
+ /** Style for the title. */
56
+ titleStyle?: TextStyle;
57
+ /** Style for the subtitle. */
58
+ subtitleStyle?: TextStyle;
59
+ /** Style for placeholder text. */
60
+ placeholderStyle?: TextStyle;
61
+ }
62
+ export declare class Interview extends Control {
63
+ private _title;
64
+ private _subtitle;
65
+ private _questions;
66
+ private _answers;
67
+ private _currentIndex;
68
+ private _input;
69
+ private _done;
70
+ private _promptStyle;
71
+ private _answeredStyle;
72
+ private _titleStyle;
73
+ private _subtitleStyle;
74
+ constructor(options: InterviewOptions);
75
+ /** Get all answers collected so far. */
76
+ get answers(): Record<string, string>;
77
+ /** Whether the interview has completed. */
78
+ get completed(): boolean;
79
+ /** Total number of questions. */
80
+ get questionCount(): number;
81
+ /** Current question index (0-based). */
82
+ get currentQuestionIndex(): number;
83
+ private _recordAnswer;
84
+ handleInput(event: InputEvent): boolean;
85
+ measure(constraint: Constraint): Size;
86
+ render(ctx: DrawingContext): void;
87
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Interview — a modal widget that presents a sequence of questions
3
+ * and collects answers. Designed to run inside a ChatView by replacing
4
+ * the normal input area (via ChatView.setInputOverride).
5
+ *
6
+ * While active the ChatView's own input prompt is hidden; when the
7
+ * interview completes it emits "complete" with all collected answers
8
+ * and the caller can remove the override to restore normal input.
9
+ *
10
+ * Usage:
11
+ *
12
+ * const interview = new Interview({
13
+ * title: "Quick intro",
14
+ * subtitle: "press Enter to skip any question",
15
+ * questions: [
16
+ * { key: "name", prompt: "Your name" },
17
+ * { key: "role", prompt: "Your role", placeholder: "e.g., senior backend engineer" },
18
+ * ],
19
+ * });
20
+ *
21
+ * chatView.setInputOverride(interview);
22
+ *
23
+ * interview.on("complete", (answers: Record<string, string>) => {
24
+ * chatView.setInputOverride(null); // restore normal input
25
+ * // use answers...
26
+ * });
27
+ */
28
+ import { Control } from "../layout/control.js";
29
+ import { TextInput } from "./text-input.js";
30
+ // ── Interview ───────────────────────────────────────────────────────
31
+ export class Interview extends Control {
32
+ _title;
33
+ _subtitle;
34
+ _questions;
35
+ _answers = new Map();
36
+ _currentIndex = 0;
37
+ _input;
38
+ _done = false;
39
+ // Styles
40
+ _promptStyle;
41
+ _answeredStyle;
42
+ _titleStyle;
43
+ _subtitleStyle;
44
+ constructor(options) {
45
+ super();
46
+ this.focusable = true;
47
+ this._title = options.title ?? "";
48
+ this._subtitle = options.subtitle ?? "";
49
+ this._questions = options.questions;
50
+ this._promptStyle = options.promptStyle ?? {};
51
+ this._answeredStyle = options.answeredStyle ?? { italic: true };
52
+ this._titleStyle = options.titleStyle ?? {};
53
+ this._subtitleStyle = options.subtitleStyle ?? { italic: true };
54
+ // Create the shared TextInput for answering
55
+ const q = this._questions[0];
56
+ this._input = new TextInput({
57
+ prompt: q ? ` ${q.prompt}: ` : "",
58
+ promptStyle: options.promptStyle ?? {},
59
+ style: options.inputStyle ?? {},
60
+ cursorStyle: options.cursorStyle ?? {},
61
+ placeholder: q?.placeholder ? ` ${q.placeholder}` : "",
62
+ placeholderStyle: options.placeholderStyle ?? { italic: true },
63
+ });
64
+ this._input.focusable = true;
65
+ this._input.onFocus();
66
+ this.addChild(this._input);
67
+ // On submit, record answer and advance
68
+ this._input.on("submit", (text) => {
69
+ this._recordAnswer(text);
70
+ });
71
+ }
72
+ // ── Public API ──────────────────────────────────────────────────
73
+ /** Get all answers collected so far. */
74
+ get answers() {
75
+ const result = {};
76
+ for (const [k, v] of this._answers) {
77
+ result[k] = v;
78
+ }
79
+ return result;
80
+ }
81
+ /** Whether the interview has completed. */
82
+ get completed() {
83
+ return this._done;
84
+ }
85
+ /** Total number of questions. */
86
+ get questionCount() {
87
+ return this._questions.length;
88
+ }
89
+ /** Current question index (0-based). */
90
+ get currentQuestionIndex() {
91
+ return this._currentIndex;
92
+ }
93
+ // ── Internal ────────────────────────────────────────────────────
94
+ _recordAnswer(text) {
95
+ if (this._done)
96
+ return;
97
+ const q = this._questions[this._currentIndex];
98
+ this._answers.set(q.key, text.trim());
99
+ this._currentIndex++;
100
+ if (this._currentIndex >= this._questions.length) {
101
+ // All questions answered
102
+ this._done = true;
103
+ this._input.visible = false;
104
+ this.invalidate();
105
+ this.emit("complete", this.answers);
106
+ return;
107
+ }
108
+ // Advance to next question
109
+ const next = this._questions[this._currentIndex];
110
+ this._input.clear();
111
+ this._input.prompt = ` ${next.prompt}: `;
112
+ this._input.placeholder = next.placeholder ? ` ${next.placeholder}` : "";
113
+ this.invalidate();
114
+ }
115
+ // ── Input handling ──────────────────────────────────────────────
116
+ handleInput(event) {
117
+ if (this._done)
118
+ return false;
119
+ return this._input.handleInput(event);
120
+ }
121
+ // ── Layout ──────────────────────────────────────────────────────
122
+ measure(constraint) {
123
+ // Height: title(1) + subtitle(1) + answered questions + current input(1)
124
+ let h = 0;
125
+ if (this._title)
126
+ h++; // title row
127
+ if (this._subtitle)
128
+ h++; // subtitle row
129
+ if (this._title || this._subtitle)
130
+ h++; // blank line after header
131
+ // Answered questions
132
+ const answeredCount = Math.min(this._currentIndex, this._questions.length);
133
+ h += answeredCount;
134
+ // Current input row (if not done)
135
+ if (!this._done) {
136
+ h++; // input row
137
+ }
138
+ const size = {
139
+ width: constraint.maxWidth,
140
+ height: Math.max(1, Math.min(h, constraint.maxHeight)),
141
+ };
142
+ this.desiredSize = size;
143
+ return size;
144
+ }
145
+ render(ctx) {
146
+ const b = this.bounds;
147
+ if (!b || b.width < 1 || b.height < 1)
148
+ return;
149
+ let y = b.y;
150
+ // Title
151
+ if (this._title && y < b.y + b.height) {
152
+ ctx.drawText(b.x + 2, y, this._title, this._titleStyle);
153
+ y++;
154
+ }
155
+ // Subtitle
156
+ if (this._subtitle && y < b.y + b.height) {
157
+ ctx.drawText(b.x + 2, y, this._subtitle, this._subtitleStyle);
158
+ y++;
159
+ }
160
+ // Blank line after header
161
+ if ((this._title || this._subtitle) && y < b.y + b.height) {
162
+ y++;
163
+ }
164
+ // Answered questions (dimmed)
165
+ const answeredCount = Math.min(this._currentIndex, this._questions.length);
166
+ for (let i = 0; i < answeredCount && y < b.y + b.height; i++) {
167
+ const q = this._questions[i];
168
+ const a = this._answers.get(q.key) || "";
169
+ const display = a || "(skipped)";
170
+ const label = ` ${q.prompt}: `;
171
+ ctx.drawText(b.x, y, label, this._promptStyle);
172
+ ctx.drawText(b.x + label.length, y, display, this._answeredStyle);
173
+ y++;
174
+ }
175
+ // Current input
176
+ if (!this._done && y < b.y + b.height) {
177
+ this._input.measure({
178
+ minWidth: 0,
179
+ maxWidth: b.width,
180
+ minHeight: 0,
181
+ maxHeight: 1,
182
+ });
183
+ this._input.arrange({ x: b.x, y, width: b.width, height: 1 });
184
+ this._input.render(ctx);
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Markdown — terminal-rendered markdown widget.
3
+ *
4
+ * Parses markdown with marked.js and renders it to styled lines
5
+ * that can be displayed in a consolonia terminal UI.
6
+ *
7
+ * Supported elements:
8
+ * - Headings (h1–h6)
9
+ * - Paragraphs with inline bold, italic, code, strikethrough
10
+ * - Links (shown as text + URL)
11
+ * - Unordered and ordered lists (nested)
12
+ * - Task lists (checkboxes)
13
+ * - Code blocks (fenced and indented)
14
+ * - Blockquotes (nested)
15
+ * - Tables (aligned columns with box-drawing borders)
16
+ * - Horizontal rules
17
+ * - Images (alt text shown)
18
+ */
19
+ import type { TextStyle } from "../drawing/context.js";
20
+ import { type SyntaxTheme } from "./syntax.js";
21
+ export interface MarkdownTheme {
22
+ /** Body text. */
23
+ text: TextStyle;
24
+ /** Bold text. */
25
+ bold: TextStyle;
26
+ /** Italic text. */
27
+ italic: TextStyle;
28
+ /** Bold+italic text. */
29
+ boldItalic: TextStyle;
30
+ /** Inline code. */
31
+ code: TextStyle;
32
+ /** Strikethrough text. */
33
+ strikethrough: TextStyle;
34
+ /** Link text. */
35
+ link: TextStyle;
36
+ /** Link URL (shown in parens). */
37
+ linkUrl: TextStyle;
38
+ /** Heading level 1. */
39
+ h1: TextStyle;
40
+ /** Heading level 2. */
41
+ h2: TextStyle;
42
+ /** Heading levels 3–6. */
43
+ h3: TextStyle;
44
+ /** Code block text. */
45
+ codeBlock: TextStyle;
46
+ /** Code block border/language label. */
47
+ codeBlockChrome: TextStyle;
48
+ /** Blockquote bar and text. */
49
+ blockquote: TextStyle;
50
+ /** List bullet/number. */
51
+ listMarker: TextStyle;
52
+ /** Table borders. */
53
+ tableBorder: TextStyle;
54
+ /** Table header text. */
55
+ tableHeader: TextStyle;
56
+ /** Horizontal rule. */
57
+ hr: TextStyle;
58
+ /** Task checkbox. */
59
+ checkbox: TextStyle;
60
+ }
61
+ /** Segment of styled text. */
62
+ interface Seg {
63
+ text: string;
64
+ style: TextStyle;
65
+ }
66
+ /** A line is an array of segments. */
67
+ type Line = Seg[];
68
+ export interface MarkdownOptions {
69
+ /** Maximum width for wrapping (default 80). */
70
+ width?: number;
71
+ /** Visual theme overrides. */
72
+ theme?: Partial<MarkdownTheme>;
73
+ /** Syntax highlighting theme overrides for code blocks. */
74
+ syntaxTheme?: Partial<SyntaxTheme>;
75
+ /** Indent string prepended to every line (default ""). */
76
+ indent?: string;
77
+ }
78
+ /**
79
+ * Render a markdown string to an array of styled lines.
80
+ *
81
+ * Each line is an array of { text, style } segments suitable for
82
+ * DrawingContext.drawStyledText() or StyledText widget.
83
+ *
84
+ * This is a pure function — no widget state, no side effects.
85
+ */
86
+ export declare function renderMarkdown(source: string, options?: MarkdownOptions): Line[];
87
+ export {};