giggles 0.3.3 → 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 +82 -3
- package/dist/ui/index.js +374 -36
- package/package.json +1 -1
package/dist/ui/index.d.ts
CHANGED
|
@@ -32,6 +32,17 @@ type TextInputProps = {
|
|
|
32
32
|
};
|
|
33
33
|
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
34
34
|
|
|
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
|
+
|
|
35
46
|
type SelectOption<T> = {
|
|
36
47
|
label: string;
|
|
37
48
|
value: T;
|
|
@@ -51,9 +62,12 @@ type SelectProps<T> = {
|
|
|
51
62
|
label?: string;
|
|
52
63
|
immediate?: boolean;
|
|
53
64
|
direction?: 'vertical' | 'horizontal';
|
|
65
|
+
maxVisible?: number;
|
|
66
|
+
paginatorStyle?: PaginatorStyle;
|
|
67
|
+
wrap?: boolean;
|
|
54
68
|
render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
|
|
55
69
|
};
|
|
56
|
-
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
70
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
57
71
|
|
|
58
72
|
type MultiSelectRenderProps<T> = {
|
|
59
73
|
option: SelectOption<T>;
|
|
@@ -69,8 +83,73 @@ type MultiSelectProps<T> = {
|
|
|
69
83
|
onHighlight?: (value: T) => void;
|
|
70
84
|
label?: string;
|
|
71
85
|
direction?: 'vertical' | 'horizontal';
|
|
86
|
+
maxVisible?: number;
|
|
87
|
+
paginatorStyle?: PaginatorStyle;
|
|
88
|
+
wrap?: boolean;
|
|
72
89
|
render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
|
|
73
90
|
};
|
|
74
|
-
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
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;
|
|
75
154
|
|
|
76
|
-
export { CommandPalette, type CommandPaletteRenderProps, MultiSelect, type MultiSelectRenderProps, Select, type SelectOption, type SelectRenderProps, TextInput, type TextInputRenderProps };
|
|
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
|
@@ -177,9 +177,104 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
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
|
|
180
184
|
import React3, { useState as useState2 } from "react";
|
|
185
|
+
import { Box as Box3 } from "ink";
|
|
186
|
+
|
|
187
|
+
// src/ui/Paginator.tsx
|
|
181
188
|
import { Box as Box2, Text as Text3 } from "ink";
|
|
182
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";
|
|
183
278
|
function Select({
|
|
184
279
|
options,
|
|
185
280
|
value,
|
|
@@ -189,6 +284,9 @@ function Select({
|
|
|
189
284
|
label,
|
|
190
285
|
immediate,
|
|
191
286
|
direction = "vertical",
|
|
287
|
+
maxVisible,
|
|
288
|
+
paginatorStyle,
|
|
289
|
+
wrap = true,
|
|
192
290
|
render
|
|
193
291
|
}) {
|
|
194
292
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -200,14 +298,14 @@ function Select({
|
|
|
200
298
|
seen.add(key);
|
|
201
299
|
}
|
|
202
300
|
const focus = useFocus();
|
|
203
|
-
const [highlightIndex, setHighlightIndex] =
|
|
301
|
+
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
204
302
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
205
303
|
if (safeIndex !== highlightIndex) {
|
|
206
304
|
setHighlightIndex(Math.max(0, safeIndex));
|
|
207
305
|
}
|
|
208
306
|
const moveHighlight = (delta) => {
|
|
209
307
|
if (options.length === 0) return;
|
|
210
|
-
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
308
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
211
309
|
if (next2 !== safeIndex) {
|
|
212
310
|
setHighlightIndex(next2);
|
|
213
311
|
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
@@ -232,27 +330,37 @@ function Select({
|
|
|
232
330
|
}
|
|
233
331
|
});
|
|
234
332
|
const isHorizontal = direction === "horizontal";
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
242
355
|
}
|
|
243
|
-
|
|
244
|
-
highlighted ? ">" : " ",
|
|
245
|
-
" ",
|
|
246
|
-
option.label
|
|
247
|
-
] }, String(option.value));
|
|
248
|
-
})
|
|
356
|
+
)
|
|
249
357
|
] });
|
|
250
358
|
}
|
|
251
359
|
|
|
252
360
|
// src/ui/MultiSelect.tsx
|
|
253
|
-
import
|
|
254
|
-
import { Box as
|
|
255
|
-
import { jsx as
|
|
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";
|
|
256
364
|
function MultiSelect({
|
|
257
365
|
options,
|
|
258
366
|
value,
|
|
@@ -261,6 +369,9 @@ function MultiSelect({
|
|
|
261
369
|
onHighlight,
|
|
262
370
|
label,
|
|
263
371
|
direction = "vertical",
|
|
372
|
+
maxVisible,
|
|
373
|
+
paginatorStyle,
|
|
374
|
+
wrap = true,
|
|
264
375
|
render
|
|
265
376
|
}) {
|
|
266
377
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -272,14 +383,14 @@ function MultiSelect({
|
|
|
272
383
|
seen.add(key);
|
|
273
384
|
}
|
|
274
385
|
const focus = useFocus();
|
|
275
|
-
const [highlightIndex, setHighlightIndex] =
|
|
386
|
+
const [highlightIndex, setHighlightIndex] = useState4(0);
|
|
276
387
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
277
388
|
if (safeIndex !== highlightIndex) {
|
|
278
389
|
setHighlightIndex(Math.max(0, safeIndex));
|
|
279
390
|
}
|
|
280
391
|
const moveHighlight = (delta) => {
|
|
281
392
|
if (options.length === 0) return;
|
|
282
|
-
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
393
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
283
394
|
if (next2 !== safeIndex) {
|
|
284
395
|
setHighlightIndex(next2);
|
|
285
396
|
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
@@ -300,27 +411,254 @@ function MultiSelect({
|
|
|
300
411
|
...onSubmit && { enter: () => onSubmit(value) }
|
|
301
412
|
});
|
|
302
413
|
const isHorizontal = direction === "horizontal";
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
310
438
|
}
|
|
311
|
-
|
|
312
|
-
highlighted ? ">" : " ",
|
|
313
|
-
" [",
|
|
314
|
-
selected ? "x" : " ",
|
|
315
|
-
"] ",
|
|
316
|
-
option.label
|
|
317
|
-
] }, String(option.value));
|
|
318
|
-
})
|
|
439
|
+
)
|
|
319
440
|
] });
|
|
320
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
|
+
}
|
|
321
654
|
export {
|
|
655
|
+
Autocomplete,
|
|
322
656
|
CommandPalette,
|
|
657
|
+
Confirm,
|
|
323
658
|
MultiSelect,
|
|
659
|
+
Paginator,
|
|
324
660
|
Select,
|
|
325
|
-
TextInput
|
|
661
|
+
TextInput,
|
|
662
|
+
Viewport,
|
|
663
|
+
VirtualList
|
|
326
664
|
};
|