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.
@@ -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] = useState2(0);
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
- return /* @__PURE__ */ jsxs3(Box2, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
236
- label != null && /* @__PURE__ */ jsx3(Text3, { children: label }),
237
- options.map((option, index) => {
238
- const highlighted = index === safeIndex;
239
- const selected = option.value === value;
240
- if (render) {
241
- return /* @__PURE__ */ jsx3(React3.Fragment, { children: render({ option, focused: focus.focused, highlighted, selected }) }, String(option.value));
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
- return /* @__PURE__ */ jsxs3(Text3, { dimColor: !focus.focused, children: [
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 React4, { useState as useState3 } from "react";
254
- import { Box as Box3, Text as Text4 } from "ink";
255
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
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] = useState3(0);
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
- return /* @__PURE__ */ jsxs4(Box3, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
304
- label != null && /* @__PURE__ */ jsx4(Text4, { children: label }),
305
- options.map((option, index) => {
306
- const highlighted = index === safeIndex;
307
- const selected = value.includes(option.value);
308
- if (render) {
309
- return /* @__PURE__ */ jsx4(React4.Fragment, { children: render({ option, focused: focus.focused, highlighted, selected }) }, String(option.value));
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
- return /* @__PURE__ */ jsxs4(Text4, { dimColor: !focus.focused, children: [
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",