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.
@@ -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
- export { CommandPalette, type CommandPaletteRenderProps, TextInput, type TextInputRenderProps };
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
- TextInput
657
+ Confirm,
658
+ MultiSelect,
659
+ Paginator,
660
+ Select,
661
+ TextInput,
662
+ Viewport,
663
+ VirtualList
180
664
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",