@flyingrobots/bijou 1.1.0 → 1.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.
- package/dist/core/components/dag-edges.d.ts +78 -0
- package/dist/core/components/dag-edges.d.ts.map +1 -0
- package/dist/core/components/dag-edges.js +127 -0
- package/dist/core/components/dag-edges.js.map +1 -0
- package/dist/core/components/dag-layout.d.ts +37 -0
- package/dist/core/components/dag-layout.d.ts.map +1 -0
- package/dist/core/components/dag-layout.js +172 -0
- package/dist/core/components/dag-layout.js.map +1 -0
- package/dist/core/components/dag-render.d.ts +51 -0
- package/dist/core/components/dag-render.d.ts.map +1 -0
- package/dist/core/components/dag-render.js +440 -0
- package/dist/core/components/dag-render.js.map +1 -0
- package/dist/core/components/dag.d.ts.map +1 -1
- package/dist/core/components/dag.js +3 -648
- package/dist/core/components/dag.js.map +1 -1
- package/dist/core/components/markdown-parse.d.ts +69 -0
- package/dist/core/components/markdown-parse.d.ts.map +1 -0
- package/dist/core/components/markdown-parse.js +272 -0
- package/dist/core/components/markdown-parse.js.map +1 -0
- package/dist/core/components/markdown-render.d.ts +20 -0
- package/dist/core/components/markdown-render.d.ts.map +1 -0
- package/dist/core/components/markdown-render.js +135 -0
- package/dist/core/components/markdown-render.js.map +1 -0
- package/dist/core/components/markdown.d.ts.map +1 -1
- package/dist/core/components/markdown.js +6 -371
- package/dist/core/components/markdown.js.map +1 -1
- package/dist/core/forms/filter-interactive.d.ts +66 -0
- package/dist/core/forms/filter-interactive.d.ts.map +1 -0
- package/dist/core/forms/filter-interactive.js +241 -0
- package/dist/core/forms/filter-interactive.js.map +1 -0
- package/dist/core/forms/filter.d.ts +2 -32
- package/dist/core/forms/filter.d.ts.map +1 -1
- package/dist/core/forms/filter.js +2 -167
- package/dist/core/forms/filter.js.map +1 -1
- package/dist/core/forms/textarea-editor.d.ts +39 -0
- package/dist/core/forms/textarea-editor.d.ts.map +1 -0
- package/dist/core/forms/textarea-editor.js +210 -0
- package/dist/core/forms/textarea-editor.js.map +1 -0
- package/dist/core/forms/textarea.d.ts +2 -19
- package/dist/core/forms/textarea.d.ts.map +1 -1
- package/dist/core/forms/textarea.js +2 -195
- package/dist/core/forms/textarea.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive filter select UI with real-time search and vim-style modes.
|
|
3
|
+
*
|
|
4
|
+
* Starts in **normal mode** where `j`/`k` navigate and any printable character
|
|
5
|
+
* (except `j`/`k`) switches to insert mode and types the character. Press `/`
|
|
6
|
+
* to enter insert mode without typing. In **insert mode**, all keys are
|
|
7
|
+
* printable (including `j`/`k`), arrow keys navigate, and Escape returns to
|
|
8
|
+
* normal mode. Ctrl+C cancels from either mode; Escape in normal mode cancels.
|
|
9
|
+
*/
|
|
10
|
+
import { formatFormTitle, terminalRenderer, createStyledFn, createBoldFn } from './form-utils.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default case-insensitive substring matcher for filter options.
|
|
13
|
+
*
|
|
14
|
+
* Check the option label first, then fall back to keyword matches.
|
|
15
|
+
*
|
|
16
|
+
* @param query - User's search input.
|
|
17
|
+
* @param option - Option to test against.
|
|
18
|
+
* @returns `true` if the query matches the label or any keyword.
|
|
19
|
+
*/
|
|
20
|
+
export function defaultMatch(query, option) {
|
|
21
|
+
const q = query.toLowerCase();
|
|
22
|
+
if (option.label.toLowerCase().includes(q))
|
|
23
|
+
return true;
|
|
24
|
+
if (option.keywords) {
|
|
25
|
+
return option.keywords.some((kw) => kw.toLowerCase().includes(q));
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Render a keyboard-navigable filter select with vim-style normal/insert modes.
|
|
31
|
+
*
|
|
32
|
+
* Starts in normal mode: `j`/`k` navigate, `/` enters insert mode, any other
|
|
33
|
+
* printable enters insert mode and types the character. In insert mode, all keys
|
|
34
|
+
* are printable, arrow keys navigate, Escape returns to normal mode. Enter
|
|
35
|
+
* confirms from either mode, Ctrl+C cancels from either mode, Escape in normal
|
|
36
|
+
* mode cancels.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Filter field configuration.
|
|
39
|
+
* @param ctx - Bijou context.
|
|
40
|
+
* @returns The value of the selected (or default/first) option.
|
|
41
|
+
*/
|
|
42
|
+
export async function interactiveFilter(options, ctx) {
|
|
43
|
+
if (options.options.length === 0) {
|
|
44
|
+
throw new Error('[bijou] filter(): options array must contain at least one option');
|
|
45
|
+
}
|
|
46
|
+
const noColor = ctx.theme.noColor;
|
|
47
|
+
const t = ctx.theme;
|
|
48
|
+
const styledFn = createStyledFn(ctx);
|
|
49
|
+
const boldFn = createBoldFn(ctx);
|
|
50
|
+
const matchFn = options.match ?? defaultMatch;
|
|
51
|
+
const maxVisible = options.maxVisible ?? 7;
|
|
52
|
+
const term = terminalRenderer(ctx);
|
|
53
|
+
let mode = 'normal';
|
|
54
|
+
let query = '';
|
|
55
|
+
let cursor = 0;
|
|
56
|
+
let scrollOffset = 0;
|
|
57
|
+
let filtered = [...options.options];
|
|
58
|
+
function applyFilter() {
|
|
59
|
+
if (query === '') {
|
|
60
|
+
filtered = [...options.options];
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
filtered = options.options.filter((opt) => matchFn(query, opt));
|
|
64
|
+
}
|
|
65
|
+
cursor = Math.min(cursor, Math.max(0, filtered.length - 1));
|
|
66
|
+
clampScroll();
|
|
67
|
+
}
|
|
68
|
+
function clampScroll() {
|
|
69
|
+
if (cursor < scrollOffset) {
|
|
70
|
+
scrollOffset = cursor;
|
|
71
|
+
}
|
|
72
|
+
else if (cursor >= scrollOffset + maxVisible) {
|
|
73
|
+
scrollOffset = cursor - maxVisible + 1;
|
|
74
|
+
}
|
|
75
|
+
scrollOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, filtered.length - maxVisible)));
|
|
76
|
+
}
|
|
77
|
+
function visibleItems() {
|
|
78
|
+
return filtered.slice(scrollOffset, scrollOffset + maxVisible);
|
|
79
|
+
}
|
|
80
|
+
function renderLineCount() {
|
|
81
|
+
return 2 + Math.min(filtered.length, maxVisible) + 1; // header + filter input + items + status
|
|
82
|
+
}
|
|
83
|
+
function render() {
|
|
84
|
+
const label = formatFormTitle(options.title, ctx);
|
|
85
|
+
term.writeLine(label);
|
|
86
|
+
// Filter input with mode indicator (: normal, / insert)
|
|
87
|
+
const indicator = mode === 'insert' ? '/' : ':';
|
|
88
|
+
const filterDisplay = query || (options.placeholder ? styledFn(t.theme.semantic.muted, options.placeholder) : '');
|
|
89
|
+
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.info, indicator)} ${filterDisplay}\n`);
|
|
90
|
+
// Visible items
|
|
91
|
+
const vis = visibleItems();
|
|
92
|
+
for (let i = 0; i < vis.length; i++) {
|
|
93
|
+
const opt = vis[i];
|
|
94
|
+
const isCurrent = scrollOffset + i === cursor;
|
|
95
|
+
const prefix = isCurrent ? '❯' : ' ';
|
|
96
|
+
if (isCurrent && !noColor) {
|
|
97
|
+
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.info, prefix)} ${boldFn(opt.label)}\n`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
ctx.io.write(`\x1b[K ${prefix} ${opt.label}\n`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Status
|
|
104
|
+
const status = `${filtered.length}/${options.options.length} items`;
|
|
105
|
+
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.muted, status)}\n`);
|
|
106
|
+
}
|
|
107
|
+
function clearRender(lineCount) {
|
|
108
|
+
term.moveUp(lineCount);
|
|
109
|
+
}
|
|
110
|
+
function clearAndRerender(prevLineCount) {
|
|
111
|
+
clearRender(prevLineCount);
|
|
112
|
+
term.clearBlock(prevLineCount);
|
|
113
|
+
render();
|
|
114
|
+
}
|
|
115
|
+
function cleanup() {
|
|
116
|
+
const total = renderLineCount();
|
|
117
|
+
clearRender(total);
|
|
118
|
+
term.clearBlock(total);
|
|
119
|
+
const selected = filtered[cursor];
|
|
120
|
+
const selectedLabel = selected ? selected.label : '(none)';
|
|
121
|
+
const label = formatFormTitle(options.title, ctx) + ' ' + styledFn(t.theme.semantic.info, selectedLabel);
|
|
122
|
+
ctx.io.write(`\x1b[K${label}\n`);
|
|
123
|
+
term.showCursor();
|
|
124
|
+
}
|
|
125
|
+
term.hideCursor();
|
|
126
|
+
render();
|
|
127
|
+
// navigateUp/navigateDown use clearRender + render instead of clearAndRerender
|
|
128
|
+
// because render() uses per-line \x1b[K (clear-to-end-of-line), making a full
|
|
129
|
+
// clearBlock unnecessary for a simple cursor position change.
|
|
130
|
+
function navigateUp() {
|
|
131
|
+
if (filtered.length === 0)
|
|
132
|
+
return;
|
|
133
|
+
const prevLineCount = renderLineCount();
|
|
134
|
+
cursor = (cursor - 1 + filtered.length) % filtered.length;
|
|
135
|
+
clampScroll();
|
|
136
|
+
clearRender(prevLineCount);
|
|
137
|
+
render();
|
|
138
|
+
}
|
|
139
|
+
function navigateDown() {
|
|
140
|
+
if (filtered.length === 0)
|
|
141
|
+
return;
|
|
142
|
+
const prevLineCount = renderLineCount();
|
|
143
|
+
cursor = (cursor + 1) % filtered.length;
|
|
144
|
+
clampScroll();
|
|
145
|
+
clearRender(prevLineCount);
|
|
146
|
+
render();
|
|
147
|
+
}
|
|
148
|
+
function typeChar(ch) {
|
|
149
|
+
const prevLineCount = renderLineCount();
|
|
150
|
+
query += ch;
|
|
151
|
+
applyFilter();
|
|
152
|
+
clearAndRerender(prevLineCount);
|
|
153
|
+
}
|
|
154
|
+
// switchMode uses the same lightweight rerender pattern as navigate functions —
|
|
155
|
+
// only the mode indicator line changes, and render() clears each line with \x1b[K.
|
|
156
|
+
function switchMode(newMode) {
|
|
157
|
+
mode = newMode;
|
|
158
|
+
const prevLineCount = renderLineCount();
|
|
159
|
+
clearRender(prevLineCount);
|
|
160
|
+
render();
|
|
161
|
+
}
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
const handle = ctx.io.rawInput((key) => {
|
|
164
|
+
// ── Mode-independent ───────────────────────────────────
|
|
165
|
+
if (key === '\r' || key === '\n') {
|
|
166
|
+
handle.dispose();
|
|
167
|
+
cleanup();
|
|
168
|
+
resolve(filtered[cursor]?.value ?? options.defaultValue ?? options.options[0].value);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (key === '\x03') {
|
|
172
|
+
// Ctrl+C — cancel from either mode
|
|
173
|
+
handle.dispose();
|
|
174
|
+
cleanup();
|
|
175
|
+
resolve(options.defaultValue ?? options.options[0].value);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// ── Normal mode ────────────────────────────────────────
|
|
179
|
+
if (mode === 'normal') {
|
|
180
|
+
if (key === '\x1b') {
|
|
181
|
+
// Escape in normal mode — cancel
|
|
182
|
+
// TODO: bare \x1b may false-trigger on slow connections where escape
|
|
183
|
+
// sequences arrive as separate bytes. Timer-based disambiguation is a
|
|
184
|
+
// shared debt with textarea-editor.ts — unify in a future PR.
|
|
185
|
+
handle.dispose();
|
|
186
|
+
cleanup();
|
|
187
|
+
resolve(options.defaultValue ?? options.options[0].value);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (key === 'j' || key === '\x1b[B') {
|
|
191
|
+
navigateDown();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (key === 'k' || key === '\x1b[A') {
|
|
195
|
+
navigateUp();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (key === '/') {
|
|
199
|
+
// Enter insert mode without typing
|
|
200
|
+
switchMode('insert');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Any other printable — enter insert mode and type the char
|
|
204
|
+
if (key.length === 1 && key >= ' ') {
|
|
205
|
+
mode = 'insert';
|
|
206
|
+
typeChar(key);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// ── Insert mode ────────────────────────────────────────
|
|
212
|
+
if (key === '\x1b') {
|
|
213
|
+
// Escape in insert mode — return to normal
|
|
214
|
+
switchMode('normal');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (key === '\x1b[A') {
|
|
218
|
+
navigateUp();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (key === '\x1b[B') {
|
|
222
|
+
navigateDown();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (key === '\x7f' || key === '\b') {
|
|
226
|
+
if (query.length > 0) {
|
|
227
|
+
const prevLineCount = renderLineCount();
|
|
228
|
+
query = query.slice(0, -1);
|
|
229
|
+
applyFilter();
|
|
230
|
+
clearAndRerender(prevLineCount);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Printable character (all printable keys including j/k)
|
|
235
|
+
if (key.length === 1 && key >= ' ') {
|
|
236
|
+
typeChar(key);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=filter-interactive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-interactive.js","sourceRoot":"","sources":["../../../src/core/forms/filter-interactive.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
* Single option in a filterable select list.
|
|
5
|
-
*
|
|
6
|
-
* @typeParam T - Type of the option's value.
|
|
7
|
-
*/
|
|
8
|
-
export interface FilterOption<T> {
|
|
9
|
-
/** Display label shown in the option list. */
|
|
10
|
-
label: string;
|
|
11
|
-
/** Value returned when this option is selected. */
|
|
12
|
-
value: T;
|
|
13
|
-
/** Additional search keywords that match this option during filtering. */
|
|
14
|
-
keywords?: string[];
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Options for the filterable select field.
|
|
18
|
-
*
|
|
19
|
-
* @typeParam T - Type of each option's value.
|
|
20
|
-
*/
|
|
21
|
-
export interface FilterOptions<T> extends FieldOptions<T> {
|
|
22
|
-
/** List of filterable options. */
|
|
23
|
-
options: FilterOption<T>[];
|
|
24
|
-
/** Placeholder text shown in the filter input when empty. */
|
|
25
|
-
placeholder?: string;
|
|
26
|
-
/** Maximum number of options visible at once. Default: 7. */
|
|
27
|
-
maxVisible?: number;
|
|
28
|
-
/** Custom match function for filtering. Defaults to case-insensitive label/keyword substring match. */
|
|
29
|
-
match?: (query: string, option: FilterOption<T>) => boolean;
|
|
30
|
-
/** Bijou context for IO, styling, and mode detection. */
|
|
31
|
-
ctx?: BijouContext;
|
|
32
|
-
}
|
|
1
|
+
export type { FilterOption, FilterOptions } from './filter-interactive.js';
|
|
2
|
+
import type { FilterOptions } from './filter-interactive.js';
|
|
33
3
|
/**
|
|
34
4
|
* Prompt the user to choose one item from a filterable list.
|
|
35
5
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/core/forms/filter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../../src/core/forms/filter.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAarE"}
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import { resolveCtx } from '../resolve-ctx.js';
|
|
2
|
-
import { formatFormTitle, renderNumberedOptions,
|
|
3
|
-
|
|
4
|
-
* Default case-insensitive substring matcher for filter options.
|
|
5
|
-
*
|
|
6
|
-
* Check the option label first, then fall back to keyword matches.
|
|
7
|
-
*
|
|
8
|
-
* @param query - User's search input.
|
|
9
|
-
* @param option - Option to test against.
|
|
10
|
-
* @returns `true` if the query matches the label or any keyword.
|
|
11
|
-
*/
|
|
12
|
-
function defaultMatch(query, option) {
|
|
13
|
-
const q = query.toLowerCase();
|
|
14
|
-
if (option.label.toLowerCase().includes(q))
|
|
15
|
-
return true;
|
|
16
|
-
if (option.keywords) {
|
|
17
|
-
return option.keywords.some((kw) => kw.toLowerCase().includes(q));
|
|
18
|
-
}
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
2
|
+
import { formatFormTitle, renderNumberedOptions, formDispatch } from './form-utils.js';
|
|
3
|
+
import { defaultMatch, interactiveFilter } from './filter-interactive.js';
|
|
21
4
|
/**
|
|
22
5
|
* Prompt the user to choose one item from a filterable list.
|
|
23
6
|
*
|
|
@@ -69,152 +52,4 @@ async function fallbackFilter(options, ctx) {
|
|
|
69
52
|
}
|
|
70
53
|
return options.defaultValue ?? options.options[0].value;
|
|
71
54
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Render a keyboard-navigable filter select using raw terminal input.
|
|
74
|
-
*
|
|
75
|
-
* Typing narrows the visible options in real time. Arrow keys navigate,
|
|
76
|
-
* Enter confirms, Backspace edits the query, Ctrl+C or Escape cancels.
|
|
77
|
-
*
|
|
78
|
-
* @param options - Filter field configuration.
|
|
79
|
-
* @param ctx - Bijou context.
|
|
80
|
-
* @returns The value of the selected (or default/first) option.
|
|
81
|
-
*/
|
|
82
|
-
async function interactiveFilter(options, ctx) {
|
|
83
|
-
const noColor = ctx.theme.noColor;
|
|
84
|
-
const t = ctx.theme;
|
|
85
|
-
const styledFn = createStyledFn(ctx);
|
|
86
|
-
const boldFn = createBoldFn(ctx);
|
|
87
|
-
const matchFn = options.match ?? defaultMatch;
|
|
88
|
-
const maxVisible = options.maxVisible ?? 7;
|
|
89
|
-
const term = terminalRenderer(ctx);
|
|
90
|
-
let query = '';
|
|
91
|
-
let cursor = 0;
|
|
92
|
-
let filtered = [...options.options];
|
|
93
|
-
function applyFilter() {
|
|
94
|
-
if (query === '') {
|
|
95
|
-
filtered = [...options.options];
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
filtered = options.options.filter((opt) => matchFn(query, opt));
|
|
99
|
-
}
|
|
100
|
-
cursor = Math.min(cursor, Math.max(0, filtered.length - 1));
|
|
101
|
-
}
|
|
102
|
-
function visibleItems() {
|
|
103
|
-
return filtered.slice(0, maxVisible);
|
|
104
|
-
}
|
|
105
|
-
function renderLineCount() {
|
|
106
|
-
return 2 + Math.min(filtered.length, maxVisible) + 1; // header + filter input + items + status
|
|
107
|
-
}
|
|
108
|
-
function render() {
|
|
109
|
-
const label = formatFormTitle(options.title, ctx);
|
|
110
|
-
term.hideCursor();
|
|
111
|
-
term.writeLine(label);
|
|
112
|
-
// Filter input
|
|
113
|
-
const filterDisplay = query || (options.placeholder ? styledFn(t.theme.semantic.muted, options.placeholder) : '');
|
|
114
|
-
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.info, '/')} ${filterDisplay}\n`);
|
|
115
|
-
// Visible items
|
|
116
|
-
const vis = visibleItems();
|
|
117
|
-
for (let i = 0; i < vis.length; i++) {
|
|
118
|
-
const opt = vis[i];
|
|
119
|
-
const isCurrent = i === cursor;
|
|
120
|
-
const prefix = isCurrent ? '❯' : ' ';
|
|
121
|
-
if (isCurrent && !noColor) {
|
|
122
|
-
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.info, prefix)} ${boldFn(opt.label)}\n`);
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
ctx.io.write(`\x1b[K ${prefix} ${opt.label}\n`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Status
|
|
129
|
-
const status = `${filtered.length}/${options.options.length} items`;
|
|
130
|
-
ctx.io.write(`\x1b[K ${styledFn(t.theme.semantic.muted, status)}\n`);
|
|
131
|
-
}
|
|
132
|
-
function clearRender() {
|
|
133
|
-
const total = renderLineCount();
|
|
134
|
-
term.moveUp(total);
|
|
135
|
-
}
|
|
136
|
-
function clearAndRerender(prevLineCount) {
|
|
137
|
-
term.moveUp(prevLineCount);
|
|
138
|
-
term.clearBlock(prevLineCount);
|
|
139
|
-
render();
|
|
140
|
-
}
|
|
141
|
-
function cleanup() {
|
|
142
|
-
const total = renderLineCount();
|
|
143
|
-
clearRender();
|
|
144
|
-
term.clearBlock(total);
|
|
145
|
-
const selected = filtered[cursor];
|
|
146
|
-
const selectedLabel = selected ? selected.label : '(none)';
|
|
147
|
-
const label = formatFormTitle(options.title, ctx) + ' ' + styledFn(t.theme.semantic.info, selectedLabel);
|
|
148
|
-
ctx.io.write(`\x1b[K${label}\n`);
|
|
149
|
-
term.showCursor();
|
|
150
|
-
}
|
|
151
|
-
render();
|
|
152
|
-
return new Promise((resolve) => {
|
|
153
|
-
const handle = ctx.io.rawInput((key) => {
|
|
154
|
-
if (key === '\r' || key === '\n') {
|
|
155
|
-
// Enter — select
|
|
156
|
-
handle.dispose();
|
|
157
|
-
cleanup();
|
|
158
|
-
resolve(filtered[cursor]?.value ?? options.defaultValue ?? options.options[0].value);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (key === '\x03' || key === '\x1b') {
|
|
162
|
-
// Ctrl+C or Escape — cancel
|
|
163
|
-
// Note: bare \x1b may false-trigger on slow connections where escape
|
|
164
|
-
// sequences arrive as separate bytes. Timer-based disambiguation is a
|
|
165
|
-
// separate future improvement.
|
|
166
|
-
handle.dispose();
|
|
167
|
-
cleanup();
|
|
168
|
-
resolve(options.defaultValue ?? options.options[0].value);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (key === '\x1b[A' || key === 'k') {
|
|
172
|
-
// Up
|
|
173
|
-
if (filtered.length === 0)
|
|
174
|
-
return;
|
|
175
|
-
cursor = (cursor - 1 + filtered.length) % filtered.length;
|
|
176
|
-
clearRender();
|
|
177
|
-
render();
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (key === '\x1b[B' || key === 'j') {
|
|
181
|
-
// Down — but only when query is empty (j is a printable char for filtering)
|
|
182
|
-
if (key === 'j' && query === '') {
|
|
183
|
-
// Start filtering with 'j'
|
|
184
|
-
query += key;
|
|
185
|
-
applyFilter();
|
|
186
|
-
clearRender();
|
|
187
|
-
render();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (key === '\x1b[B') {
|
|
191
|
-
if (filtered.length === 0)
|
|
192
|
-
return;
|
|
193
|
-
cursor = (cursor + 1) % filtered.length;
|
|
194
|
-
clearRender();
|
|
195
|
-
render();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
// j when query is non-empty — fall through to printable handler
|
|
199
|
-
}
|
|
200
|
-
if (key === '\x7f' || key === '\b') {
|
|
201
|
-
// Backspace
|
|
202
|
-
if (query.length > 0) {
|
|
203
|
-
const prevLineCount = renderLineCount();
|
|
204
|
-
query = query.slice(0, -1);
|
|
205
|
-
applyFilter();
|
|
206
|
-
clearAndRerender(prevLineCount);
|
|
207
|
-
}
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// Printable character (filter input)
|
|
211
|
-
if (key.length === 1 && key >= ' ') {
|
|
212
|
-
const prevLineCount = renderLineCount();
|
|
213
|
-
query += key;
|
|
214
|
-
applyFilter();
|
|
215
|
-
clearAndRerender(prevLineCount);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
55
|
//# sourceMappingURL=filter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/core/forms/filter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../../../src/core/forms/filter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI1E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAI,OAAyB;IACvD,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,YAAY,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAEpC,OAAO,YAAY,CACjB,GAAG,EACH,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC,EACpC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAClC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,cAAc,CAAI,OAAyB,EAAE,GAAiB;IAC3E,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACzD,qBAAqB,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAE9B,8BAA8B;IAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC7C,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAE,CAAC,KAAK,CAAC;IACrC,CAAC;IAED,uBAAuB;IACvB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QACrE,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IACpC,CAAC;IAED,OAAO,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive textarea editor state machine.
|
|
3
|
+
*
|
|
4
|
+
* Renders a scrollable multi-line editor with cursor navigation,
|
|
5
|
+
* optional line numbers, placeholder text, and max-length guard.
|
|
6
|
+
* Ctrl+D submits; Ctrl+C or Escape cancels.
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldOptions } from './types.js';
|
|
9
|
+
import type { BijouContext } from '../../ports/context.js';
|
|
10
|
+
/**
|
|
11
|
+
* Options for the multi-line textarea field.
|
|
12
|
+
*/
|
|
13
|
+
export interface TextareaOptions extends FieldOptions<string> {
|
|
14
|
+
/** Placeholder text shown when the textarea is empty. */
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Maximum character count (including newlines). */
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
/** Display line numbers in the gutter. Default: false. */
|
|
19
|
+
showLineNumbers?: boolean;
|
|
20
|
+
/** Visible editor height in lines. Default: 6. */
|
|
21
|
+
height?: number;
|
|
22
|
+
/** Render width in columns. Default: 80. */
|
|
23
|
+
width?: number;
|
|
24
|
+
/** Bijou context for IO, styling, and mode detection. */
|
|
25
|
+
ctx?: BijouContext;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render a full interactive textarea editor using raw terminal input.
|
|
29
|
+
*
|
|
30
|
+
* Supports arrow-key cursor movement, scrolling, line wrapping,
|
|
31
|
+
* optional line numbers, placeholder text, and a max-length guard.
|
|
32
|
+
* Ctrl+D submits; Ctrl+C or Escape cancels.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Textarea field configuration.
|
|
35
|
+
* @param ctx - Bijou context.
|
|
36
|
+
* @returns The entered text (newline-joined), or the default value on cancel.
|
|
37
|
+
*/
|
|
38
|
+
export declare function interactiveTextarea(options: TextareaOptions, ctx: BijouContext): Promise<string>;
|
|
39
|
+
//# sourceMappingURL=textarea-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textarea-editor.d.ts","sourceRoot":"","sources":["../../../src/core/forms/textarea-editor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,YAAY,CAAC,MAAM,CAAC;IAC3D,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAsMtG"}
|