giggles 0.3.2 → 0.3.4
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/ui/index.d.ts +121 -1
- package/dist/ui/index.js +485 -1
- package/package.json +1 -1
package/dist/ui/index.d.ts
CHANGED
|
@@ -32,4 +32,124 @@ type TextInputProps = {
|
|
|
32
32
|
};
|
|
33
33
|
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
type PaginatorStyle = 'arrows' | 'scrollbar' | 'counter';
|
|
36
|
+
type PaginatorProps = {
|
|
37
|
+
total: number;
|
|
38
|
+
offset: number;
|
|
39
|
+
visible: number;
|
|
40
|
+
style?: PaginatorStyle;
|
|
41
|
+
position?: 'above' | 'below';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
declare function Paginator({ total, offset, visible, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
|
|
45
|
+
|
|
46
|
+
type SelectOption<T> = {
|
|
47
|
+
label: string;
|
|
48
|
+
value: T;
|
|
49
|
+
};
|
|
50
|
+
type SelectRenderProps<T> = {
|
|
51
|
+
option: SelectOption<T>;
|
|
52
|
+
focused: boolean;
|
|
53
|
+
highlighted: boolean;
|
|
54
|
+
selected: boolean;
|
|
55
|
+
};
|
|
56
|
+
type SelectProps<T> = {
|
|
57
|
+
options: SelectOption<T>[];
|
|
58
|
+
value: T;
|
|
59
|
+
onChange: (value: T) => void;
|
|
60
|
+
onSubmit?: (value: T) => void;
|
|
61
|
+
onHighlight?: (value: T) => void;
|
|
62
|
+
label?: string;
|
|
63
|
+
immediate?: boolean;
|
|
64
|
+
direction?: 'vertical' | 'horizontal';
|
|
65
|
+
maxVisible?: number;
|
|
66
|
+
paginatorStyle?: PaginatorStyle;
|
|
67
|
+
wrap?: boolean;
|
|
68
|
+
render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
|
|
69
|
+
};
|
|
70
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
71
|
+
|
|
72
|
+
type MultiSelectRenderProps<T> = {
|
|
73
|
+
option: SelectOption<T>;
|
|
74
|
+
focused: boolean;
|
|
75
|
+
highlighted: boolean;
|
|
76
|
+
selected: boolean;
|
|
77
|
+
};
|
|
78
|
+
type MultiSelectProps<T> = {
|
|
79
|
+
options: SelectOption<T>[];
|
|
80
|
+
value: T[];
|
|
81
|
+
onChange: (value: T[]) => void;
|
|
82
|
+
onSubmit?: (value: T[]) => void;
|
|
83
|
+
onHighlight?: (value: T) => void;
|
|
84
|
+
label?: string;
|
|
85
|
+
direction?: 'vertical' | 'horizontal';
|
|
86
|
+
maxVisible?: number;
|
|
87
|
+
paginatorStyle?: PaginatorStyle;
|
|
88
|
+
wrap?: boolean;
|
|
89
|
+
render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
|
|
90
|
+
};
|
|
91
|
+
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
92
|
+
|
|
93
|
+
type ConfirmProps = {
|
|
94
|
+
message: string;
|
|
95
|
+
defaultValue?: boolean;
|
|
96
|
+
onSubmit: (value: boolean) => void;
|
|
97
|
+
};
|
|
98
|
+
declare function Confirm({ message, defaultValue, onSubmit }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
|
|
100
|
+
type AutocompleteRenderProps<T> = {
|
|
101
|
+
option: SelectOption<T>;
|
|
102
|
+
focused: boolean;
|
|
103
|
+
highlighted: boolean;
|
|
104
|
+
selected: boolean;
|
|
105
|
+
};
|
|
106
|
+
type AutocompleteProps<T> = {
|
|
107
|
+
options: SelectOption<T>[];
|
|
108
|
+
value: T;
|
|
109
|
+
onChange: (value: T) => void;
|
|
110
|
+
onSubmit?: (value: T) => void;
|
|
111
|
+
onHighlight?: (value: T) => void;
|
|
112
|
+
label?: string;
|
|
113
|
+
placeholder?: string;
|
|
114
|
+
filter?: (query: string, option: SelectOption<T>) => boolean;
|
|
115
|
+
maxVisible?: number;
|
|
116
|
+
paginatorStyle?: PaginatorStyle;
|
|
117
|
+
wrap?: boolean;
|
|
118
|
+
render?: (props: AutocompleteRenderProps<T>) => React__default.ReactNode;
|
|
119
|
+
};
|
|
120
|
+
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
121
|
+
|
|
122
|
+
type VirtualListRenderProps<T> = {
|
|
123
|
+
item: T;
|
|
124
|
+
index: number;
|
|
125
|
+
};
|
|
126
|
+
type VirtualListBase<T> = {
|
|
127
|
+
items: T[];
|
|
128
|
+
maxVisible?: number;
|
|
129
|
+
paginatorStyle?: PaginatorStyle;
|
|
130
|
+
render: (props: VirtualListRenderProps<T>) => React__default.ReactNode;
|
|
131
|
+
};
|
|
132
|
+
type VirtualListProps<T> = VirtualListBase<T> & ({
|
|
133
|
+
highlightIndex?: number;
|
|
134
|
+
scrollOffset?: never;
|
|
135
|
+
} | {
|
|
136
|
+
highlightIndex?: never;
|
|
137
|
+
scrollOffset?: number;
|
|
138
|
+
});
|
|
139
|
+
declare function VirtualList<T>({ items, highlightIndex, scrollOffset: controlledOffset, maxVisible, paginatorStyle, render }: VirtualListProps<T>): react_jsx_runtime.JSX.Element;
|
|
140
|
+
|
|
141
|
+
type ViewportRenderProps<T> = {
|
|
142
|
+
item: T;
|
|
143
|
+
index: number;
|
|
144
|
+
focused: boolean;
|
|
145
|
+
};
|
|
146
|
+
type ViewportProps<T> = {
|
|
147
|
+
items: T[];
|
|
148
|
+
maxVisible: number;
|
|
149
|
+
showLineNumbers?: boolean;
|
|
150
|
+
paginatorStyle?: PaginatorStyle;
|
|
151
|
+
render?: (props: ViewportRenderProps<T>) => React__default.ReactNode;
|
|
152
|
+
};
|
|
153
|
+
declare function Viewport<T>({ items, maxVisible, showLineNumbers, paginatorStyle, render }: ViewportProps<T>): react_jsx_runtime.JSX.Element;
|
|
154
|
+
|
|
155
|
+
export { Autocomplete, type AutocompleteRenderProps, CommandPalette, type CommandPaletteRenderProps, Confirm, MultiSelect, type MultiSelectRenderProps, Paginator, type PaginatorStyle, Select, type SelectOption, type SelectRenderProps, TextInput, type TextInputRenderProps, Viewport, type ViewportRenderProps, VirtualList, type VirtualListRenderProps };
|
package/dist/ui/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FocusTrap,
|
|
3
|
+
GigglesError,
|
|
3
4
|
useFocus,
|
|
4
5
|
useKeybindingRegistry,
|
|
5
6
|
useKeybindings
|
|
@@ -174,7 +175,490 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
|
174
175
|
/* @__PURE__ */ jsx2(Text2, { dimColor: isPlaceholder, children: displayValue })
|
|
175
176
|
] });
|
|
176
177
|
}
|
|
178
|
+
|
|
179
|
+
// src/ui/Select.tsx
|
|
180
|
+
import React4, { useState as useState3 } from "react";
|
|
181
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
182
|
+
|
|
183
|
+
// src/ui/VirtualList.tsx
|
|
184
|
+
import React3, { useState as useState2 } from "react";
|
|
185
|
+
import { Box as Box3 } from "ink";
|
|
186
|
+
|
|
187
|
+
// src/ui/Paginator.tsx
|
|
188
|
+
import { Box as Box2, Text as Text3 } from "ink";
|
|
189
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
190
|
+
function Paginator({ total, offset, visible, style = "arrows", position }) {
|
|
191
|
+
if (total <= visible) return null;
|
|
192
|
+
const hasAbove = offset > 0;
|
|
193
|
+
const hasBelow = offset + visible < total;
|
|
194
|
+
if (style === "arrows") {
|
|
195
|
+
if (position === "above" && hasAbove) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2191" });
|
|
196
|
+
if (position === "below" && hasBelow) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2193" });
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
if (style === "counter") {
|
|
200
|
+
if (position === "above") return null;
|
|
201
|
+
return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
202
|
+
offset + 1,
|
|
203
|
+
"\u2013",
|
|
204
|
+
Math.min(offset + visible, total),
|
|
205
|
+
" of ",
|
|
206
|
+
total
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
const thumbSize = Math.max(1, Math.round(visible / total * visible));
|
|
210
|
+
const maxThumbOffset = visible - thumbSize;
|
|
211
|
+
const maxScrollOffset = total - visible;
|
|
212
|
+
const thumbOffset = maxScrollOffset === 0 ? 0 : Math.round(offset / maxScrollOffset * maxThumbOffset);
|
|
213
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: Array.from({ length: visible }, (_, i) => {
|
|
214
|
+
const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
|
|
215
|
+
return /* @__PURE__ */ jsx3(Text3, { dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
|
|
216
|
+
}) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/ui/VirtualList.tsx
|
|
220
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
221
|
+
function VirtualList({
|
|
222
|
+
items,
|
|
223
|
+
highlightIndex,
|
|
224
|
+
scrollOffset: controlledOffset,
|
|
225
|
+
maxVisible,
|
|
226
|
+
paginatorStyle = "arrows",
|
|
227
|
+
render
|
|
228
|
+
}) {
|
|
229
|
+
const [internalOffset, setInternalOffset] = useState2(0);
|
|
230
|
+
if (maxVisible == null || items.length <= maxVisible) {
|
|
231
|
+
return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index)) });
|
|
232
|
+
}
|
|
233
|
+
const maxOffset = Math.max(0, items.length - maxVisible);
|
|
234
|
+
let offset;
|
|
235
|
+
if (controlledOffset != null) {
|
|
236
|
+
offset = Math.min(controlledOffset, maxOffset);
|
|
237
|
+
} else {
|
|
238
|
+
offset = Math.min(internalOffset, maxOffset);
|
|
239
|
+
if (highlightIndex != null && highlightIndex >= 0) {
|
|
240
|
+
if (highlightIndex < offset) {
|
|
241
|
+
offset = highlightIndex;
|
|
242
|
+
} else if (highlightIndex >= offset + maxVisible) {
|
|
243
|
+
offset = highlightIndex - maxVisible + 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (offset !== internalOffset) {
|
|
247
|
+
setInternalOffset(offset);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const visible = items.slice(offset, offset + maxVisible);
|
|
251
|
+
const paginatorProps = {
|
|
252
|
+
total: items.length,
|
|
253
|
+
offset,
|
|
254
|
+
visible: maxVisible,
|
|
255
|
+
style: paginatorStyle
|
|
256
|
+
};
|
|
257
|
+
if (paginatorStyle === "scrollbar") {
|
|
258
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "row", gap: 1, children: [
|
|
259
|
+
/* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: visible.map((item, i) => {
|
|
260
|
+
const index = offset + i;
|
|
261
|
+
return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
|
|
262
|
+
}) }),
|
|
263
|
+
/* @__PURE__ */ jsx4(Paginator, { ...paginatorProps })
|
|
264
|
+
] });
|
|
265
|
+
}
|
|
266
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
|
|
267
|
+
/* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "above" }),
|
|
268
|
+
visible.map((item, i) => {
|
|
269
|
+
const index = offset + i;
|
|
270
|
+
return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
|
|
271
|
+
}),
|
|
272
|
+
/* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "below" })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/ui/Select.tsx
|
|
277
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
278
|
+
function Select({
|
|
279
|
+
options,
|
|
280
|
+
value,
|
|
281
|
+
onChange,
|
|
282
|
+
onSubmit,
|
|
283
|
+
onHighlight,
|
|
284
|
+
label,
|
|
285
|
+
immediate,
|
|
286
|
+
direction = "vertical",
|
|
287
|
+
maxVisible,
|
|
288
|
+
paginatorStyle,
|
|
289
|
+
wrap = true,
|
|
290
|
+
render
|
|
291
|
+
}) {
|
|
292
|
+
const seen = /* @__PURE__ */ new Set();
|
|
293
|
+
for (const opt of options) {
|
|
294
|
+
const key = String(opt.value);
|
|
295
|
+
if (seen.has(key)) {
|
|
296
|
+
throw new GigglesError("Select options must have unique values");
|
|
297
|
+
}
|
|
298
|
+
seen.add(key);
|
|
299
|
+
}
|
|
300
|
+
const focus = useFocus();
|
|
301
|
+
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
302
|
+
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
303
|
+
if (safeIndex !== highlightIndex) {
|
|
304
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
305
|
+
}
|
|
306
|
+
const moveHighlight = (delta) => {
|
|
307
|
+
if (options.length === 0) return;
|
|
308
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
309
|
+
if (next2 !== safeIndex) {
|
|
310
|
+
setHighlightIndex(next2);
|
|
311
|
+
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
312
|
+
if (immediate) {
|
|
313
|
+
onChange(options[next2].value);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const prev = () => moveHighlight(-1);
|
|
318
|
+
const next = () => moveHighlight(1);
|
|
319
|
+
const navBindings = direction === "vertical" ? { j: next, k: prev, down: next, up: prev } : { l: next, h: prev, right: next, left: prev };
|
|
320
|
+
useKeybindings(focus, {
|
|
321
|
+
...navBindings,
|
|
322
|
+
enter: () => {
|
|
323
|
+
if (options.length === 0) return;
|
|
324
|
+
if (immediate) {
|
|
325
|
+
onSubmit == null ? void 0 : onSubmit(options[safeIndex].value);
|
|
326
|
+
} else {
|
|
327
|
+
onChange(options[safeIndex].value);
|
|
328
|
+
onSubmit == null ? void 0 : onSubmit(options[safeIndex].value);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
const isHorizontal = direction === "horizontal";
|
|
333
|
+
const renderOption = ({ item: option, index }) => {
|
|
334
|
+
const highlighted = index === safeIndex;
|
|
335
|
+
const selected = option.value === value;
|
|
336
|
+
if (render) {
|
|
337
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
338
|
+
}
|
|
339
|
+
return /* @__PURE__ */ jsxs5(Text4, { dimColor: !focus.focused, children: [
|
|
340
|
+
highlighted ? ">" : " ",
|
|
341
|
+
" ",
|
|
342
|
+
option.label
|
|
343
|
+
] }, String(option.value));
|
|
344
|
+
};
|
|
345
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
346
|
+
label != null && /* @__PURE__ */ jsx5(Text4, { children: label }),
|
|
347
|
+
isHorizontal ? options.map((option, index) => /* @__PURE__ */ jsx5(React4.Fragment, { children: renderOption({ item: option, index }) }, String(option.value))) : /* @__PURE__ */ jsx5(
|
|
348
|
+
VirtualList,
|
|
349
|
+
{
|
|
350
|
+
items: options,
|
|
351
|
+
highlightIndex: safeIndex,
|
|
352
|
+
maxVisible,
|
|
353
|
+
paginatorStyle,
|
|
354
|
+
render: renderOption
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
] });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/ui/MultiSelect.tsx
|
|
361
|
+
import React5, { useState as useState4 } from "react";
|
|
362
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
363
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
364
|
+
function MultiSelect({
|
|
365
|
+
options,
|
|
366
|
+
value,
|
|
367
|
+
onChange,
|
|
368
|
+
onSubmit,
|
|
369
|
+
onHighlight,
|
|
370
|
+
label,
|
|
371
|
+
direction = "vertical",
|
|
372
|
+
maxVisible,
|
|
373
|
+
paginatorStyle,
|
|
374
|
+
wrap = true,
|
|
375
|
+
render
|
|
376
|
+
}) {
|
|
377
|
+
const seen = /* @__PURE__ */ new Set();
|
|
378
|
+
for (const opt of options) {
|
|
379
|
+
const key = String(opt.value);
|
|
380
|
+
if (seen.has(key)) {
|
|
381
|
+
throw new GigglesError("MultiSelect options must have unique values");
|
|
382
|
+
}
|
|
383
|
+
seen.add(key);
|
|
384
|
+
}
|
|
385
|
+
const focus = useFocus();
|
|
386
|
+
const [highlightIndex, setHighlightIndex] = useState4(0);
|
|
387
|
+
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
388
|
+
if (safeIndex !== highlightIndex) {
|
|
389
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
390
|
+
}
|
|
391
|
+
const moveHighlight = (delta) => {
|
|
392
|
+
if (options.length === 0) return;
|
|
393
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
394
|
+
if (next2 !== safeIndex) {
|
|
395
|
+
setHighlightIndex(next2);
|
|
396
|
+
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const toggle = () => {
|
|
400
|
+
if (options.length === 0) return;
|
|
401
|
+
const item = options[safeIndex].value;
|
|
402
|
+
const exists = value.includes(item);
|
|
403
|
+
onChange(exists ? value.filter((v) => v !== item) : [...value, item]);
|
|
404
|
+
};
|
|
405
|
+
const prev = () => moveHighlight(-1);
|
|
406
|
+
const next = () => moveHighlight(1);
|
|
407
|
+
const navBindings = direction === "vertical" ? { j: next, k: prev, down: next, up: prev } : { l: next, h: prev, right: next, left: prev };
|
|
408
|
+
useKeybindings(focus, {
|
|
409
|
+
...navBindings,
|
|
410
|
+
" ": toggle,
|
|
411
|
+
...onSubmit && { enter: () => onSubmit(value) }
|
|
412
|
+
});
|
|
413
|
+
const isHorizontal = direction === "horizontal";
|
|
414
|
+
const renderOption = ({ item: option, index }) => {
|
|
415
|
+
const highlighted = index === safeIndex;
|
|
416
|
+
const selected = value.includes(option.value);
|
|
417
|
+
if (render) {
|
|
418
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
419
|
+
}
|
|
420
|
+
return /* @__PURE__ */ jsxs6(Text5, { dimColor: !focus.focused, children: [
|
|
421
|
+
highlighted ? ">" : " ",
|
|
422
|
+
" [",
|
|
423
|
+
selected ? "x" : " ",
|
|
424
|
+
"] ",
|
|
425
|
+
option.label
|
|
426
|
+
] }, String(option.value));
|
|
427
|
+
};
|
|
428
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
429
|
+
label != null && /* @__PURE__ */ jsx6(Text5, { children: label }),
|
|
430
|
+
isHorizontal ? options.map((option, index) => /* @__PURE__ */ jsx6(React5.Fragment, { children: renderOption({ item: option, index }) }, String(option.value))) : /* @__PURE__ */ jsx6(
|
|
431
|
+
VirtualList,
|
|
432
|
+
{
|
|
433
|
+
items: options,
|
|
434
|
+
highlightIndex: safeIndex,
|
|
435
|
+
maxVisible,
|
|
436
|
+
paginatorStyle,
|
|
437
|
+
render: renderOption
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/ui/Confirm.tsx
|
|
444
|
+
import { Text as Text6 } from "ink";
|
|
445
|
+
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
446
|
+
function Confirm({ message, defaultValue = true, onSubmit }) {
|
|
447
|
+
const focus = useFocus();
|
|
448
|
+
useKeybindings(focus, {
|
|
449
|
+
y: () => onSubmit(true),
|
|
450
|
+
n: () => onSubmit(false),
|
|
451
|
+
enter: () => onSubmit(defaultValue)
|
|
452
|
+
});
|
|
453
|
+
const hint = defaultValue ? "Y/n" : "y/N";
|
|
454
|
+
return /* @__PURE__ */ jsxs7(Text6, { dimColor: !focus.focused, children: [
|
|
455
|
+
message,
|
|
456
|
+
" (",
|
|
457
|
+
hint,
|
|
458
|
+
")"
|
|
459
|
+
] });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/ui/Autocomplete.tsx
|
|
463
|
+
import { useReducer as useReducer2, useRef as useRef2, useState as useState5 } from "react";
|
|
464
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
465
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
466
|
+
function defaultFilter(query, option) {
|
|
467
|
+
const caseSensitive = query !== query.toLowerCase();
|
|
468
|
+
if (caseSensitive) {
|
|
469
|
+
return option.label.includes(query);
|
|
470
|
+
}
|
|
471
|
+
return option.label.toLowerCase().includes(query);
|
|
472
|
+
}
|
|
473
|
+
function Autocomplete({
|
|
474
|
+
options,
|
|
475
|
+
value,
|
|
476
|
+
onChange,
|
|
477
|
+
onSubmit,
|
|
478
|
+
onHighlight,
|
|
479
|
+
label,
|
|
480
|
+
placeholder,
|
|
481
|
+
filter = defaultFilter,
|
|
482
|
+
maxVisible,
|
|
483
|
+
paginatorStyle,
|
|
484
|
+
wrap = true,
|
|
485
|
+
render
|
|
486
|
+
}) {
|
|
487
|
+
const seen = /* @__PURE__ */ new Set();
|
|
488
|
+
for (const opt of options) {
|
|
489
|
+
const key = String(opt.value);
|
|
490
|
+
if (seen.has(key)) {
|
|
491
|
+
throw new GigglesError("Autocomplete options must have unique values");
|
|
492
|
+
}
|
|
493
|
+
seen.add(key);
|
|
494
|
+
}
|
|
495
|
+
const focus = useFocus();
|
|
496
|
+
const [query, setQuery] = useState5("");
|
|
497
|
+
const [highlightIndex, setHighlightIndex] = useState5(0);
|
|
498
|
+
const cursorRef = useRef2(0);
|
|
499
|
+
const [, forceRender] = useReducer2((c) => c + 1, 0);
|
|
500
|
+
const filtered = query.length === 0 ? options : options.filter((opt) => filter(query, opt));
|
|
501
|
+
const safeIndex = filtered.length === 0 ? -1 : Math.min(highlightIndex, filtered.length - 1);
|
|
502
|
+
if (safeIndex >= 0 && safeIndex !== highlightIndex) {
|
|
503
|
+
setHighlightIndex(safeIndex);
|
|
504
|
+
}
|
|
505
|
+
const cursor = Math.min(cursorRef.current, query.length);
|
|
506
|
+
cursorRef.current = cursor;
|
|
507
|
+
const moveHighlight = (delta) => {
|
|
508
|
+
if (filtered.length === 0) return;
|
|
509
|
+
const next = wrap ? (safeIndex + delta + filtered.length) % filtered.length : Math.max(0, Math.min(filtered.length - 1, safeIndex + delta));
|
|
510
|
+
if (next !== safeIndex) {
|
|
511
|
+
setHighlightIndex(next);
|
|
512
|
+
onHighlight == null ? void 0 : onHighlight(filtered[next].value);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const updateQuery = (newQuery) => {
|
|
516
|
+
setQuery(newQuery);
|
|
517
|
+
setHighlightIndex(0);
|
|
518
|
+
};
|
|
519
|
+
useKeybindings(
|
|
520
|
+
focus,
|
|
521
|
+
{
|
|
522
|
+
up: () => moveHighlight(-1),
|
|
523
|
+
down: () => moveHighlight(1),
|
|
524
|
+
left: () => {
|
|
525
|
+
cursorRef.current = Math.max(0, cursorRef.current - 1);
|
|
526
|
+
forceRender();
|
|
527
|
+
},
|
|
528
|
+
right: () => {
|
|
529
|
+
cursorRef.current = Math.min(query.length, cursorRef.current + 1);
|
|
530
|
+
forceRender();
|
|
531
|
+
},
|
|
532
|
+
home: () => {
|
|
533
|
+
cursorRef.current = 0;
|
|
534
|
+
forceRender();
|
|
535
|
+
},
|
|
536
|
+
end: () => {
|
|
537
|
+
cursorRef.current = query.length;
|
|
538
|
+
forceRender();
|
|
539
|
+
},
|
|
540
|
+
backspace: () => {
|
|
541
|
+
const c = cursorRef.current;
|
|
542
|
+
if (c > 0) {
|
|
543
|
+
cursorRef.current = c - 1;
|
|
544
|
+
updateQuery(query.slice(0, c - 1) + query.slice(c));
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
delete: () => {
|
|
548
|
+
const c = cursorRef.current;
|
|
549
|
+
if (c < query.length) {
|
|
550
|
+
updateQuery(query.slice(0, c) + query.slice(c + 1));
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
enter: () => {
|
|
554
|
+
if (filtered.length === 0) return;
|
|
555
|
+
const selected = filtered[safeIndex].value;
|
|
556
|
+
onChange(selected);
|
|
557
|
+
onSubmit == null ? void 0 : onSubmit(selected);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
capture: true,
|
|
562
|
+
passthrough: ["tab", "shift+tab", "escape"],
|
|
563
|
+
onKeypress: (input, key) => {
|
|
564
|
+
if (input.length === 1 && !key.ctrl && !key.return && !key.escape && !key.tab) {
|
|
565
|
+
const c = cursorRef.current;
|
|
566
|
+
cursorRef.current = c + 1;
|
|
567
|
+
updateQuery(query.slice(0, c) + input + query.slice(c));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
const before = query.slice(0, cursor);
|
|
573
|
+
const cursorChar = query[cursor] ?? " ";
|
|
574
|
+
const after = query.slice(cursor + 1);
|
|
575
|
+
const prefix = label != null ? `${label} ` : "";
|
|
576
|
+
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
577
|
+
focus.focused ? /* @__PURE__ */ jsxs8(Text7, { children: [
|
|
578
|
+
prefix,
|
|
579
|
+
before,
|
|
580
|
+
/* @__PURE__ */ jsx7(Text7, { inverse: true, children: cursorChar }),
|
|
581
|
+
after
|
|
582
|
+
] }) : /* @__PURE__ */ jsxs8(Text7, { dimColor: true, children: [
|
|
583
|
+
prefix,
|
|
584
|
+
query.length > 0 ? query : placeholder ?? ""
|
|
585
|
+
] }),
|
|
586
|
+
/* @__PURE__ */ jsx7(
|
|
587
|
+
VirtualList,
|
|
588
|
+
{
|
|
589
|
+
items: filtered,
|
|
590
|
+
highlightIndex: safeIndex,
|
|
591
|
+
maxVisible,
|
|
592
|
+
paginatorStyle,
|
|
593
|
+
render: ({ item: option, index }) => {
|
|
594
|
+
const highlighted = index === safeIndex;
|
|
595
|
+
const selected = option.value === value;
|
|
596
|
+
if (render) {
|
|
597
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
598
|
+
}
|
|
599
|
+
return /* @__PURE__ */ jsxs8(Text7, { dimColor: !focus.focused, children: [
|
|
600
|
+
highlighted ? ">" : " ",
|
|
601
|
+
" ",
|
|
602
|
+
option.label
|
|
603
|
+
] });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
)
|
|
607
|
+
] });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/ui/Viewport.tsx
|
|
611
|
+
import { useState as useState6 } from "react";
|
|
612
|
+
import { Text as Text8 } from "ink";
|
|
613
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
614
|
+
function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }) {
|
|
615
|
+
const focus = useFocus();
|
|
616
|
+
const [scrollOffset, setScrollOffset] = useState6(0);
|
|
617
|
+
const maxOffset = Math.max(0, items.length - maxVisible);
|
|
618
|
+
const scroll = (delta) => {
|
|
619
|
+
setScrollOffset((prev) => Math.max(0, Math.min(maxOffset, prev + delta)));
|
|
620
|
+
};
|
|
621
|
+
useKeybindings(focus, {
|
|
622
|
+
j: () => scroll(1),
|
|
623
|
+
k: () => scroll(-1),
|
|
624
|
+
down: () => scroll(1),
|
|
625
|
+
up: () => scroll(-1),
|
|
626
|
+
pagedown: () => scroll(maxVisible),
|
|
627
|
+
pageup: () => scroll(-maxVisible),
|
|
628
|
+
g: () => setScrollOffset(0),
|
|
629
|
+
G: () => setScrollOffset(maxOffset)
|
|
630
|
+
});
|
|
631
|
+
const gutterWidth = showLineNumbers ? String(items.length).length + 1 : 0;
|
|
632
|
+
return /* @__PURE__ */ jsx8(
|
|
633
|
+
VirtualList,
|
|
634
|
+
{
|
|
635
|
+
items,
|
|
636
|
+
scrollOffset,
|
|
637
|
+
maxVisible,
|
|
638
|
+
paginatorStyle,
|
|
639
|
+
render: ({ item, index }) => {
|
|
640
|
+
if (render) {
|
|
641
|
+
return render({ item, index, focused: focus.focused });
|
|
642
|
+
}
|
|
643
|
+
return /* @__PURE__ */ jsxs9(Text8, { dimColor: !focus.focused, children: [
|
|
644
|
+
showLineNumbers && /* @__PURE__ */ jsxs9(Text8, { dimColor: true, children: [
|
|
645
|
+
String(index + 1).padStart(gutterWidth - 1),
|
|
646
|
+
" "
|
|
647
|
+
] }),
|
|
648
|
+
String(item)
|
|
649
|
+
] });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
);
|
|
653
|
+
}
|
|
177
654
|
export {
|
|
655
|
+
Autocomplete,
|
|
178
656
|
CommandPalette,
|
|
179
|
-
|
|
657
|
+
Confirm,
|
|
658
|
+
MultiSelect,
|
|
659
|
+
Paginator,
|
|
660
|
+
Select,
|
|
661
|
+
TextInput,
|
|
662
|
+
Viewport,
|
|
663
|
+
VirtualList
|
|
180
664
|
};
|