giggles 0.3.4 → 0.3.6

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.
@@ -452,6 +452,30 @@ function FocusTrap({ children }) {
452
452
  return /* @__PURE__ */ jsx5(FocusNodeContext.Provider, { value: id, children });
453
453
  }
454
454
 
455
+ // src/core/theme.tsx
456
+ import { createContext as createContext4, useContext as useContext4 } from "react";
457
+ import { jsx as jsx6 } from "react/jsx-runtime";
458
+ var defaultTheme = {
459
+ accentColor: "#6B9FD4",
460
+ selectedColor: "#8FBF7F",
461
+ hintColor: "#8A8A8A",
462
+ hintDimColor: "#5C5C5C",
463
+ hintHighlightColor: "#D4D4D4",
464
+ hintHighlightDimColor: "#A0A0A0",
465
+ indicator: "\u25B8",
466
+ checkedIndicator: "\u2713",
467
+ uncheckedIndicator: "\u25CB"
468
+ };
469
+ var ThemeContext = createContext4(defaultTheme);
470
+ function ThemeProvider({ theme, children }) {
471
+ const parent = useContext4(ThemeContext);
472
+ const merged = { ...parent, ...theme };
473
+ return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value: merged, children });
474
+ }
475
+ function useTheme() {
476
+ return useContext4(ThemeContext);
477
+ }
478
+
455
479
  export {
456
480
  GigglesError,
457
481
  FocusProvider,
@@ -464,5 +488,7 @@ export {
464
488
  FocusTrap,
465
489
  useFocus,
466
490
  FocusGroup,
467
- useFocusState
491
+ useFocusState,
492
+ ThemeProvider,
493
+ useTheme
468
494
  };
package/dist/index.d.ts CHANGED
@@ -9,10 +9,29 @@ declare class GigglesError extends Error {
9
9
  constructor(message: string);
10
10
  }
11
11
 
12
+ type GigglesTheme = {
13
+ accentColor: string;
14
+ selectedColor: string;
15
+ hintColor: string;
16
+ hintDimColor: string;
17
+ hintHighlightColor: string;
18
+ hintHighlightDimColor: string;
19
+ indicator: string;
20
+ checkedIndicator: string;
21
+ uncheckedIndicator: string;
22
+ };
23
+ type ThemeProviderProps = {
24
+ theme?: Partial<GigglesTheme>;
25
+ children: React__default.ReactNode;
26
+ };
27
+ declare function ThemeProvider({ theme, children }: ThemeProviderProps): react_jsx_runtime.JSX.Element;
28
+ declare function useTheme(): GigglesTheme;
29
+
12
30
  type GigglesProviderProps = {
31
+ theme?: Partial<GigglesTheme>;
13
32
  children: React__default.ReactNode;
14
33
  };
15
- declare function GigglesProvider({ children }: GigglesProviderProps): react_jsx_runtime.JSX.Element;
34
+ declare function GigglesProvider({ theme, children }: GigglesProviderProps): react_jsx_runtime.JSX.Element;
16
35
 
17
36
  declare function useKeybindings(focus: FocusHandle, bindings: Keybindings, options?: KeybindingOptions): void;
18
37
 
@@ -79,4 +98,4 @@ type NavigationContextValue = {
79
98
 
80
99
  declare const useNavigation: () => NavigationContextValue;
81
100
 
82
- export { FocusGroup, type FocusHandle, FocusTrap, GigglesError, GigglesProvider, KeybindingOptions, type KeybindingRegistry, Keybindings, type NavigationContextValue, RegisteredKeybinding, Router, Screen, useFocus, useFocusState, useKeybindingRegistry, useKeybindings, useNavigation };
101
+ export { FocusGroup, type FocusHandle, FocusTrap, GigglesError, GigglesProvider, type GigglesTheme, KeybindingOptions, type KeybindingRegistry, Keybindings, type NavigationContextValue, RegisteredKeybinding, Router, Screen, ThemeProvider, useFocus, useFocusState, useKeybindingRegistry, useKeybindings, useNavigation, useTheme };
package/dist/index.js CHANGED
@@ -9,17 +9,19 @@ import {
9
9
  GigglesError,
10
10
  InputProvider,
11
11
  InputRouter,
12
+ ThemeProvider,
12
13
  useFocus,
13
14
  useFocusContext,
14
15
  useFocusState,
15
16
  useKeybindingRegistry,
16
- useKeybindings
17
- } from "./chunk-OYQZHF73.js";
17
+ useKeybindings,
18
+ useTheme
19
+ } from "./chunk-FEQYHTG7.js";
18
20
 
19
21
  // src/core/GigglesProvider.tsx
20
22
  import { jsx } from "react/jsx-runtime";
21
- function GigglesProvider({ children }) {
22
- return /* @__PURE__ */ jsx(AlternateScreen, { children: /* @__PURE__ */ jsx(FocusProvider, { children: /* @__PURE__ */ jsx(InputProvider, { children: /* @__PURE__ */ jsx(InputRouter, { children }) }) }) });
23
+ function GigglesProvider({ theme, children }) {
24
+ return /* @__PURE__ */ jsx(AlternateScreen, { children: /* @__PURE__ */ jsx(ThemeProvider, { theme, children: /* @__PURE__ */ jsx(FocusProvider, { children: /* @__PURE__ */ jsx(InputProvider, { children: /* @__PURE__ */ jsx(InputRouter, { children }) }) }) }) });
23
25
  }
24
26
 
25
27
  // src/core/router/Router.tsx
@@ -181,9 +183,11 @@ export {
181
183
  GigglesProvider,
182
184
  Router,
183
185
  Screen,
186
+ ThemeProvider,
184
187
  useFocus,
185
188
  useFocusState,
186
189
  useKeybindingRegistry,
187
190
  useKeybindings,
188
- useNavigation
191
+ useNavigation,
192
+ useTheme
189
193
  };
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React__default from 'react';
3
2
  import { R as RegisteredKeybinding } from '../types-Dmw9TKt4.js';
3
+ import React__default from 'react';
4
4
  import 'ink';
5
5
 
6
6
  type CommandPaletteRenderProps = {
@@ -10,10 +10,11 @@ type CommandPaletteRenderProps = {
10
10
  onSelect: (cmd: RegisteredKeybinding) => void;
11
11
  };
12
12
  type CommandPaletteProps = {
13
- onClose: () => void;
14
- render?: (props: CommandPaletteRenderProps) => React__default.ReactNode;
13
+ onClose?: () => void;
14
+ interactive?: boolean;
15
+ render?: (props: CommandPaletteRenderProps) => React.ReactNode;
15
16
  };
16
- declare function CommandPalette({ onClose, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
17
+ declare function CommandPalette({ onClose, interactive, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
17
18
 
18
19
  type TextInputRenderProps = {
19
20
  value: string;
@@ -32,16 +33,17 @@ type TextInputProps = {
32
33
  };
33
34
  declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
34
35
 
35
- type PaginatorStyle = 'arrows' | 'scrollbar' | 'counter';
36
+ type PaginatorStyle = 'dots' | 'arrows' | 'scrollbar' | 'counter' | 'none';
36
37
  type PaginatorProps = {
37
38
  total: number;
38
39
  offset: number;
39
40
  visible: number;
41
+ gap?: number;
40
42
  style?: PaginatorStyle;
41
43
  position?: 'above' | 'below';
42
44
  };
43
45
 
44
- declare function Paginator({ total, offset, visible, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
46
+ declare function Paginator({ total, offset, visible, gap, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
45
47
 
46
48
  type SelectOption<T> = {
47
49
  label: string;
@@ -62,12 +64,13 @@ type SelectProps<T> = {
62
64
  label?: string;
63
65
  immediate?: boolean;
64
66
  direction?: 'vertical' | 'horizontal';
67
+ gap?: number;
65
68
  maxVisible?: number;
66
69
  paginatorStyle?: PaginatorStyle;
67
70
  wrap?: boolean;
68
71
  render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
69
72
  };
70
- declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
73
+ declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, gap, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
71
74
 
72
75
  type MultiSelectRenderProps<T> = {
73
76
  option: SelectOption<T>;
@@ -83,12 +86,13 @@ type MultiSelectProps<T> = {
83
86
  onHighlight?: (value: T) => void;
84
87
  label?: string;
85
88
  direction?: 'vertical' | 'horizontal';
89
+ gap?: number;
86
90
  maxVisible?: number;
87
91
  paginatorStyle?: PaginatorStyle;
88
92
  wrap?: boolean;
89
93
  render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
90
94
  };
91
- declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
95
+ declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, gap, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
92
96
 
93
97
  type ConfirmProps = {
94
98
  message: string;
@@ -112,12 +116,13 @@ type AutocompleteProps<T> = {
112
116
  label?: string;
113
117
  placeholder?: string;
114
118
  filter?: (query: string, option: SelectOption<T>) => boolean;
119
+ gap?: number;
115
120
  maxVisible?: number;
116
121
  paginatorStyle?: PaginatorStyle;
117
122
  wrap?: boolean;
118
123
  render?: (props: AutocompleteRenderProps<T>) => React__default.ReactNode;
119
124
  };
120
- declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
125
+ declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, gap, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
121
126
 
122
127
  type VirtualListRenderProps<T> = {
123
128
  item: T;
@@ -125,6 +130,7 @@ type VirtualListRenderProps<T> = {
125
130
  };
126
131
  type VirtualListBase<T> = {
127
132
  items: T[];
133
+ gap?: number;
128
134
  maxVisible?: number;
129
135
  paginatorStyle?: PaginatorStyle;
130
136
  render: (props: VirtualListRenderProps<T>) => React__default.ReactNode;
@@ -136,7 +142,7 @@ type VirtualListProps<T> = VirtualListBase<T> & ({
136
142
  highlightIndex?: never;
137
143
  scrollOffset?: number;
138
144
  });
139
- declare function VirtualList<T>({ items, highlightIndex, scrollOffset: controlledOffset, maxVisible, paginatorStyle, render }: VirtualListProps<T>): react_jsx_runtime.JSX.Element;
145
+ declare function VirtualList<T>({ items, highlightIndex, scrollOffset: controlledOffset, gap, maxVisible, paginatorStyle, render }: VirtualListProps<T>): react_jsx_runtime.JSX.Element;
140
146
 
141
147
  type ViewportRenderProps<T> = {
142
148
  item: T;
@@ -147,9 +153,10 @@ type ViewportProps<T> = {
147
153
  items: T[];
148
154
  maxVisible: number;
149
155
  showLineNumbers?: boolean;
156
+ gap?: number;
150
157
  paginatorStyle?: PaginatorStyle;
151
158
  render?: (props: ViewportRenderProps<T>) => React__default.ReactNode;
152
159
  };
153
- declare function Viewport<T>({ items, maxVisible, showLineNumbers, paginatorStyle, render }: ViewportProps<T>): react_jsx_runtime.JSX.Element;
160
+ declare function Viewport<T>({ items, maxVisible, showLineNumbers, gap, paginatorStyle, render }: ViewportProps<T>): react_jsx_runtime.JSX.Element;
154
161
 
155
162
  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
@@ -3,8 +3,9 @@ import {
3
3
  GigglesError,
4
4
  useFocus,
5
5
  useKeybindingRegistry,
6
- useKeybindings
7
- } from "../chunk-OYQZHF73.js";
6
+ useKeybindings,
7
+ useTheme
8
+ } from "../chunk-FEQYHTG7.js";
8
9
 
9
10
  // src/ui/CommandPalette.tsx
10
11
  import { useState } from "react";
@@ -44,10 +45,12 @@ function fuzzyMatch(name, query) {
44
45
  }
45
46
  function Inner({ onClose, render }) {
46
47
  const focus = useFocus();
48
+ const theme = useTheme();
47
49
  const [query, setQuery] = useState("");
48
50
  const [selectedIndex, setSelectedIndex] = useState(0);
49
51
  const registry = useKeybindingRegistry();
50
- const filtered = registry.all.filter((cmd) => fuzzyMatch(cmd.name, query));
52
+ const named = registry.all.filter((cmd) => cmd.name != null);
53
+ const filtered = named.filter((cmd) => fuzzyMatch(cmd.name, query));
51
54
  const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
52
55
  const onSelect = (cmd) => {
53
56
  cmd.handler("", EMPTY_KEY);
@@ -61,8 +64,8 @@ function Inner({ onClose, render }) {
61
64
  const cmd = filtered[clampedIndex];
62
65
  if (cmd) onSelect(cmd);
63
66
  },
64
- up: () => setSelectedIndex((i) => Math.max(0, i - 1)),
65
- down: () => setSelectedIndex((i) => Math.max(0, Math.min(filtered.length - 1, i + 1))),
67
+ left: () => setSelectedIndex((i) => (i - 1 + filtered.length) % filtered.length),
68
+ right: () => setSelectedIndex((i) => (i + 1) % filtered.length),
66
69
  backspace: () => {
67
70
  setQuery((q) => q.slice(0, -1));
68
71
  setSelectedIndex(0);
@@ -81,20 +84,48 @@ function Inner({ onClose, render }) {
81
84
  if (render) {
82
85
  return /* @__PURE__ */ jsx(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
83
86
  }
84
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", width: 40, children: [
85
- /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
87
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
88
+ query.length > 0 && /* @__PURE__ */ jsxs(Text, { children: [
86
89
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
87
90
  /* @__PURE__ */ jsx(Text, { children: query }),
88
91
  /* @__PURE__ */ jsx(Text, { inverse: true, children: " " })
89
92
  ] }),
90
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No commands found" }) }) : filtered.map((cmd, i) => /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [
91
- /* @__PURE__ */ jsx(Text, { inverse: i === clampedIndex, children: cmd.name }),
92
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: cmd.key })
93
- ] }, `${cmd.nodeId}-${cmd.key}`)) })
93
+ filtered.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No commands found" }) : /* @__PURE__ */ jsx(Box, { flexWrap: "wrap", children: filtered.map((cmd, index) => {
94
+ const highlighted = index === clampedIndex;
95
+ const keyColor = highlighted ? theme.hintHighlightColor : theme.hintColor;
96
+ const labelColor = highlighted ? theme.hintHighlightDimColor : theme.hintDimColor;
97
+ return /* @__PURE__ */ jsxs(Text, { children: [
98
+ /* @__PURE__ */ jsx(Text, { color: keyColor, bold: true, children: cmd.key }),
99
+ /* @__PURE__ */ jsxs(Text, { color: labelColor, children: [
100
+ " ",
101
+ cmd.name
102
+ ] }),
103
+ index < filtered.length - 1 && /* @__PURE__ */ jsx(Text, { color: theme.hintDimColor, children: " \u2022 " })
104
+ ] }, `${cmd.nodeId}-${cmd.key}`);
105
+ }) })
94
106
  ] });
95
107
  }
96
- function CommandPalette({ onClose, render }) {
97
- return /* @__PURE__ */ jsx(FocusTrap, { children: /* @__PURE__ */ jsx(Inner, { onClose, render }) });
108
+ function HintsBar() {
109
+ const registry = useKeybindingRegistry();
110
+ const theme = useTheme();
111
+ const commands = registry.available.filter((cmd) => cmd.name != null);
112
+ if (commands.length === 0) return null;
113
+ return /* @__PURE__ */ jsx(Box, { flexWrap: "wrap", children: commands.map((cmd, index) => /* @__PURE__ */ jsxs(Text, { children: [
114
+ /* @__PURE__ */ jsx(Text, { color: theme.hintColor, bold: true, children: cmd.key }),
115
+ /* @__PURE__ */ jsxs(Text, { color: theme.hintDimColor, children: [
116
+ " ",
117
+ cmd.name
118
+ ] }),
119
+ index < commands.length - 1 && /* @__PURE__ */ jsx(Text, { color: theme.hintDimColor, children: " \u2022 " })
120
+ ] }, `${cmd.nodeId}-${cmd.key}`)) });
121
+ }
122
+ var noop = () => {
123
+ };
124
+ function CommandPalette({ onClose, interactive = true, render }) {
125
+ if (!interactive) {
126
+ return /* @__PURE__ */ jsx(HintsBar, {});
127
+ }
128
+ return /* @__PURE__ */ jsx(FocusTrap, { children: /* @__PURE__ */ jsx(Inner, { onClose: onClose ?? noop, render }) });
98
129
  }
99
130
 
100
131
  // src/ui/TextInput.tsx
@@ -161,41 +192,57 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
161
192
  }
162
193
  const displayValue = value.length > 0 ? value : placeholder ?? "";
163
194
  const isPlaceholder = value.length === 0;
164
- const prefix = label != null ? `${label} ` : "";
165
195
  if (focus.focused) {
166
196
  return /* @__PURE__ */ jsxs2(Text2, { children: [
167
- prefix,
197
+ label != null && /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
198
+ label,
199
+ " "
200
+ ] }),
168
201
  before,
169
202
  /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
170
203
  after
171
204
  ] });
172
205
  }
173
- return /* @__PURE__ */ jsxs2(Text2, { children: [
174
- prefix,
175
- /* @__PURE__ */ jsx2(Text2, { dimColor: isPlaceholder, children: displayValue })
206
+ return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
207
+ label != null && /* @__PURE__ */ jsxs2(Text2, { children: [
208
+ label,
209
+ " "
210
+ ] }),
211
+ isPlaceholder ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: displayValue }) : displayValue
176
212
  ] });
177
213
  }
178
214
 
179
215
  // src/ui/Select.tsx
180
- import React4, { useState as useState3 } from "react";
216
+ import React3, { useEffect as useEffect2, useState as useState3 } from "react";
181
217
  import { Box as Box4, Text as Text4 } from "ink";
182
218
 
183
219
  // src/ui/VirtualList.tsx
184
- import React3, { useState as useState2 } from "react";
220
+ import React2, { useEffect, useRef as useRef2, useState as useState2 } from "react";
185
221
  import { Box as Box3 } from "ink";
186
222
 
187
223
  // src/ui/Paginator.tsx
188
224
  import { Box as Box2, Text as Text3 } from "ink";
189
225
  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;
226
+ function Paginator({ total, offset, visible, gap = 0, style = "arrows", position }) {
227
+ const theme = useTheme();
228
+ if (style === "none" || total <= visible) return null;
192
229
  const hasAbove = offset > 0;
193
230
  const hasBelow = offset + visible < total;
194
231
  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" });
232
+ if (position === "above" && hasAbove) return /* @__PURE__ */ jsx3(Text3, { color: theme.accentColor, children: "\u2191" });
233
+ if (position === "below" && hasBelow) return /* @__PURE__ */ jsx3(Text3, { color: theme.accentColor, children: "\u2193" });
197
234
  return null;
198
235
  }
236
+ if (style === "dots") {
237
+ if (position === "above") return null;
238
+ const totalPages = Math.ceil(total / visible);
239
+ const maxOffset = Math.max(1, total - visible);
240
+ const currentPage = Math.round(offset / maxOffset * (totalPages - 1));
241
+ return /* @__PURE__ */ jsx3(Text3, { children: Array.from({ length: totalPages }, (_, i) => /* @__PURE__ */ jsxs3(Text3, { color: i === currentPage ? theme.accentColor : void 0, dimColor: i !== currentPage, children: [
242
+ i === currentPage ? "\u25CF" : "\u25CB",
243
+ i < totalPages - 1 ? " " : ""
244
+ ] }, i)) });
245
+ }
199
246
  if (style === "counter") {
200
247
  if (position === "above") return null;
201
248
  return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
@@ -206,13 +253,14 @@ function Paginator({ total, offset, visible, style = "arrows", position }) {
206
253
  total
207
254
  ] });
208
255
  }
209
- const thumbSize = Math.max(1, Math.round(visible / total * visible));
210
- const maxThumbOffset = visible - thumbSize;
256
+ const totalLines = visible + gap * (visible - 1);
257
+ const thumbSize = Math.max(1, Math.round(visible / total * totalLines));
258
+ const maxThumbOffset = totalLines - thumbSize;
211
259
  const maxScrollOffset = total - visible;
212
260
  const thumbOffset = maxScrollOffset === 0 ? 0 : Math.round(offset / maxScrollOffset * maxThumbOffset);
213
- return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: Array.from({ length: visible }, (_, i) => {
261
+ return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: Array.from({ length: totalLines }, (_, i) => {
214
262
  const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
215
- return /* @__PURE__ */ jsx3(Text3, { dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
263
+ return /* @__PURE__ */ jsx3(Text3, { color: isThumb ? theme.accentColor : void 0, dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
216
264
  }) });
217
265
  }
218
266
 
@@ -222,52 +270,61 @@ function VirtualList({
222
270
  items,
223
271
  highlightIndex,
224
272
  scrollOffset: controlledOffset,
273
+ gap = 0,
225
274
  maxVisible,
226
- paginatorStyle = "arrows",
275
+ paginatorStyle = "dots",
227
276
  render
228
277
  }) {
229
278
  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;
279
+ const offsetRef = useRef2(0);
280
+ const windowed = maxVisible != null && items.length > maxVisible;
281
+ let offset = 0;
282
+ if (windowed) {
283
+ const maxOffset = Math.max(0, items.length - maxVisible);
284
+ if (controlledOffset != null) {
285
+ offset = Math.min(controlledOffset, maxOffset);
286
+ } else {
287
+ offset = Math.min(internalOffset, maxOffset);
288
+ if (highlightIndex != null && highlightIndex >= 0) {
289
+ if (highlightIndex < offset) {
290
+ offset = highlightIndex;
291
+ } else if (highlightIndex >= offset + maxVisible) {
292
+ offset = highlightIndex - maxVisible + 1;
293
+ }
244
294
  }
245
295
  }
246
- if (offset !== internalOffset) {
247
- setInternalOffset(offset);
296
+ }
297
+ offsetRef.current = offset;
298
+ useEffect(() => {
299
+ if (windowed && offsetRef.current !== internalOffset) {
300
+ setInternalOffset(offsetRef.current);
248
301
  }
302
+ }, [windowed, highlightIndex, controlledOffset, internalOffset]);
303
+ if (!windowed) {
304
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", gap, children: items.map((item, index) => /* @__PURE__ */ jsx4(React2.Fragment, { children: render({ item, index }) }, index)) });
249
305
  }
250
306
  const visible = items.slice(offset, offset + maxVisible);
251
307
  const paginatorProps = {
252
308
  total: items.length,
253
309
  offset,
254
310
  visible: maxVisible,
311
+ gap,
255
312
  style: paginatorStyle
256
313
  };
257
314
  if (paginatorStyle === "scrollbar") {
258
315
  return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "row", gap: 1, children: [
259
- /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: visible.map((item, i) => {
316
+ /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", gap, flexGrow: 1, children: visible.map((item, i) => {
260
317
  const index = offset + i;
261
- return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
318
+ return /* @__PURE__ */ jsx4(React2.Fragment, { children: render({ item, index }) }, index);
262
319
  }) }),
263
320
  /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps })
264
321
  ] });
265
322
  }
266
- return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
323
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", gap, children: [
267
324
  /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "above" }),
268
325
  visible.map((item, i) => {
269
326
  const index = offset + i;
270
- return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
327
+ return /* @__PURE__ */ jsx4(React2.Fragment, { children: render({ item, index }) }, index);
271
328
  }),
272
329
  /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "below" })
273
330
  ] });
@@ -284,6 +341,7 @@ function Select({
284
341
  label,
285
342
  immediate,
286
343
  direction = "vertical",
344
+ gap,
287
345
  maxVisible,
288
346
  paginatorStyle,
289
347
  wrap = true,
@@ -298,11 +356,14 @@ function Select({
298
356
  seen.add(key);
299
357
  }
300
358
  const focus = useFocus();
359
+ const theme = useTheme();
301
360
  const [highlightIndex, setHighlightIndex] = useState3(0);
302
361
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
303
- if (safeIndex !== highlightIndex) {
304
- setHighlightIndex(Math.max(0, safeIndex));
305
- }
362
+ useEffect2(() => {
363
+ if (safeIndex !== highlightIndex) {
364
+ setHighlightIndex(Math.max(0, safeIndex));
365
+ }
366
+ }, [safeIndex, highlightIndex]);
306
367
  const moveHighlight = (delta) => {
307
368
  if (options.length === 0) return;
308
369
  const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
@@ -336,19 +397,17 @@ function Select({
336
397
  if (render) {
337
398
  return render({ option, focused: focus.focused, highlighted, selected });
338
399
  }
339
- return /* @__PURE__ */ jsxs5(Text4, { dimColor: !focus.focused, children: [
340
- highlighted ? ">" : " ",
341
- " ",
342
- option.label
343
- ] }, String(option.value));
400
+ const active = highlighted && focus.focused;
401
+ return /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsx5(Text4, { dimColor: !focus.focused && !highlighted, children: /* @__PURE__ */ jsx5(Text4, { color: active ? theme.accentColor : void 0, bold: highlighted, children: option.label }) }) }, String(option.value));
344
402
  };
345
403
  return /* @__PURE__ */ jsxs5(Box4, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
346
404
  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(
405
+ isHorizontal ? options.map((option, index) => /* @__PURE__ */ jsx5(React3.Fragment, { children: renderOption({ item: option, index }) }, String(option.value))) : /* @__PURE__ */ jsx5(
348
406
  VirtualList,
349
407
  {
350
408
  items: options,
351
409
  highlightIndex: safeIndex,
410
+ gap,
352
411
  maxVisible,
353
412
  paginatorStyle,
354
413
  render: renderOption
@@ -358,7 +417,7 @@ function Select({
358
417
  }
359
418
 
360
419
  // src/ui/MultiSelect.tsx
361
- import React5, { useState as useState4 } from "react";
420
+ import React4, { useEffect as useEffect3, useState as useState4 } from "react";
362
421
  import { Box as Box5, Text as Text5 } from "ink";
363
422
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
364
423
  function MultiSelect({
@@ -369,6 +428,7 @@ function MultiSelect({
369
428
  onHighlight,
370
429
  label,
371
430
  direction = "vertical",
431
+ gap,
372
432
  maxVisible,
373
433
  paginatorStyle,
374
434
  wrap = true,
@@ -383,11 +443,14 @@ function MultiSelect({
383
443
  seen.add(key);
384
444
  }
385
445
  const focus = useFocus();
446
+ const theme = useTheme();
386
447
  const [highlightIndex, setHighlightIndex] = useState4(0);
387
448
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
388
- if (safeIndex !== highlightIndex) {
389
- setHighlightIndex(Math.max(0, safeIndex));
390
- }
449
+ useEffect3(() => {
450
+ if (safeIndex !== highlightIndex) {
451
+ setHighlightIndex(Math.max(0, safeIndex));
452
+ }
453
+ }, [safeIndex, highlightIndex]);
391
454
  const moveHighlight = (delta) => {
392
455
  if (options.length === 0) return;
393
456
  const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
@@ -417,21 +480,21 @@ function MultiSelect({
417
480
  if (render) {
418
481
  return render({ option, focused: focus.focused, highlighted, selected });
419
482
  }
420
- return /* @__PURE__ */ jsxs6(Text5, { dimColor: !focus.focused, children: [
421
- highlighted ? ">" : " ",
422
- " [",
423
- selected ? "x" : " ",
424
- "] ",
425
- option.label
426
- ] }, String(option.value));
483
+ const active = highlighted && focus.focused;
484
+ return /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs6(Text5, { dimColor: !focus.focused && !highlighted, children: [
485
+ /* @__PURE__ */ jsx6(Text5, { color: selected ? theme.selectedColor : void 0, bold: selected, children: selected ? theme.checkedIndicator : theme.uncheckedIndicator }),
486
+ " ",
487
+ /* @__PURE__ */ jsx6(Text5, { color: active ? theme.accentColor : void 0, bold: highlighted, children: option.label })
488
+ ] }) }, String(option.value));
427
489
  };
428
490
  return /* @__PURE__ */ jsxs6(Box5, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
429
491
  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(
492
+ isHorizontal ? options.map((option, index) => /* @__PURE__ */ jsx6(React4.Fragment, { children: renderOption({ item: option, index }) }, String(option.value))) : /* @__PURE__ */ jsx6(
431
493
  VirtualList,
432
494
  {
433
495
  items: options,
434
496
  highlightIndex: safeIndex,
497
+ gap,
435
498
  maxVisible,
436
499
  paginatorStyle,
437
500
  render: renderOption
@@ -453,14 +516,17 @@ function Confirm({ message, defaultValue = true, onSubmit }) {
453
516
  const hint = defaultValue ? "Y/n" : "y/N";
454
517
  return /* @__PURE__ */ jsxs7(Text6, { dimColor: !focus.focused, children: [
455
518
  message,
456
- " (",
457
- hint,
458
- ")"
519
+ " ",
520
+ /* @__PURE__ */ jsxs7(Text6, { dimColor: true, children: [
521
+ "(",
522
+ hint,
523
+ ")"
524
+ ] })
459
525
  ] });
460
526
  }
461
527
 
462
528
  // src/ui/Autocomplete.tsx
463
- import { useReducer as useReducer2, useRef as useRef2, useState as useState5 } from "react";
529
+ import { useEffect as useEffect4, useMemo, useReducer as useReducer2, useRef as useRef3, useState as useState5 } from "react";
464
530
  import { Box as Box6, Text as Text7 } from "ink";
465
531
  import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
466
532
  function defaultFilter(query, option) {
@@ -479,6 +545,7 @@ function Autocomplete({
479
545
  label,
480
546
  placeholder,
481
547
  filter = defaultFilter,
548
+ gap,
482
549
  maxVisible,
483
550
  paginatorStyle,
484
551
  wrap = true,
@@ -493,15 +560,21 @@ function Autocomplete({
493
560
  seen.add(key);
494
561
  }
495
562
  const focus = useFocus();
563
+ const theme = useTheme();
496
564
  const [query, setQuery] = useState5("");
497
565
  const [highlightIndex, setHighlightIndex] = useState5(0);
498
- const cursorRef = useRef2(0);
566
+ const cursorRef = useRef3(0);
499
567
  const [, forceRender] = useReducer2((c) => c + 1, 0);
500
- const filtered = query.length === 0 ? options : options.filter((opt) => filter(query, opt));
568
+ const filtered = useMemo(
569
+ () => query.length === 0 ? options : options.filter((opt) => filter(query, opt)),
570
+ [options, query, filter]
571
+ );
501
572
  const safeIndex = filtered.length === 0 ? -1 : Math.min(highlightIndex, filtered.length - 1);
502
- if (safeIndex >= 0 && safeIndex !== highlightIndex) {
503
- setHighlightIndex(safeIndex);
504
- }
573
+ useEffect4(() => {
574
+ if (safeIndex >= 0 && safeIndex !== highlightIndex) {
575
+ setHighlightIndex(safeIndex);
576
+ }
577
+ }, [safeIndex, highlightIndex]);
505
578
  const cursor = Math.min(cursorRef.current, query.length);
506
579
  cursorRef.current = cursor;
507
580
  const moveHighlight = (delta) => {
@@ -572,15 +645,20 @@ function Autocomplete({
572
645
  const before = query.slice(0, cursor);
573
646
  const cursorChar = query[cursor] ?? " ";
574
647
  const after = query.slice(cursor + 1);
575
- const prefix = label != null ? `${label} ` : "";
576
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
648
+ return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", gap: 1, children: [
577
649
  focus.focused ? /* @__PURE__ */ jsxs8(Text7, { children: [
578
- prefix,
650
+ label != null && /* @__PURE__ */ jsxs8(Text7, { bold: true, children: [
651
+ label,
652
+ " "
653
+ ] }),
579
654
  before,
580
655
  /* @__PURE__ */ jsx7(Text7, { inverse: true, children: cursorChar }),
581
656
  after
582
657
  ] }) : /* @__PURE__ */ jsxs8(Text7, { dimColor: true, children: [
583
- prefix,
658
+ label != null && /* @__PURE__ */ jsxs8(Text7, { children: [
659
+ label,
660
+ " "
661
+ ] }),
584
662
  query.length > 0 ? query : placeholder ?? ""
585
663
  ] }),
586
664
  /* @__PURE__ */ jsx7(
@@ -588,6 +666,7 @@ function Autocomplete({
588
666
  {
589
667
  items: filtered,
590
668
  highlightIndex: safeIndex,
669
+ gap,
591
670
  maxVisible,
592
671
  paginatorStyle,
593
672
  render: ({ item: option, index }) => {
@@ -596,11 +675,8 @@ function Autocomplete({
596
675
  if (render) {
597
676
  return render({ option, focused: focus.focused, highlighted, selected });
598
677
  }
599
- return /* @__PURE__ */ jsxs8(Text7, { dimColor: !focus.focused, children: [
600
- highlighted ? ">" : " ",
601
- " ",
602
- option.label
603
- ] });
678
+ const active = highlighted && focus.focused;
679
+ return /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsx7(Text7, { dimColor: !focus.focused && !highlighted, children: /* @__PURE__ */ jsx7(Text7, { color: active ? theme.accentColor : void 0, bold: highlighted, children: option.label }) }) });
604
680
  }
605
681
  }
606
682
  )
@@ -611,7 +687,7 @@ function Autocomplete({
611
687
  import { useState as useState6 } from "react";
612
688
  import { Text as Text8 } from "ink";
613
689
  import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
614
- function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }) {
690
+ function Viewport({ items, maxVisible, showLineNumbers, gap, paginatorStyle, render }) {
615
691
  const focus = useFocus();
616
692
  const [scrollOffset, setScrollOffset] = useState6(0);
617
693
  const maxOffset = Math.max(0, items.length - maxVisible);
@@ -634,6 +710,7 @@ function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }
634
710
  {
635
711
  items,
636
712
  scrollOffset,
713
+ gap,
637
714
  maxVisible,
638
715
  paginatorStyle,
639
716
  render: ({ item, index }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",