giggles 0.3.5 → 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,19 +1,8 @@
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
- type PaginatorStyle = 'arrows' | 'scrollbar' | 'counter';
7
- type PaginatorProps = {
8
- total: number;
9
- offset: number;
10
- visible: number;
11
- style?: PaginatorStyle;
12
- position?: 'above' | 'below';
13
- };
14
-
15
- declare function Paginator({ total, offset, visible, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
16
-
17
6
  type CommandPaletteRenderProps = {
18
7
  query: string;
19
8
  filtered: RegisteredKeybinding[];
@@ -21,12 +10,11 @@ type CommandPaletteRenderProps = {
21
10
  onSelect: (cmd: RegisteredKeybinding) => void;
22
11
  };
23
12
  type CommandPaletteProps = {
24
- onClose: () => void;
25
- maxVisible?: number;
26
- paginatorStyle?: PaginatorStyle;
27
- render?: (props: CommandPaletteRenderProps) => React__default.ReactNode;
13
+ onClose?: () => void;
14
+ interactive?: boolean;
15
+ render?: (props: CommandPaletteRenderProps) => React.ReactNode;
28
16
  };
29
- declare function CommandPalette({ onClose, maxVisible, paginatorStyle, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
17
+ declare function CommandPalette({ onClose, interactive, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
30
18
 
31
19
  type TextInputRenderProps = {
32
20
  value: string;
@@ -45,6 +33,18 @@ type TextInputProps = {
45
33
  };
46
34
  declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
47
35
 
36
+ type PaginatorStyle = 'dots' | 'arrows' | 'scrollbar' | 'counter' | 'none';
37
+ type PaginatorProps = {
38
+ total: number;
39
+ offset: number;
40
+ visible: number;
41
+ gap?: number;
42
+ style?: PaginatorStyle;
43
+ position?: 'above' | 'below';
44
+ };
45
+
46
+ declare function Paginator({ total, offset, visible, gap, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
47
+
48
48
  type SelectOption<T> = {
49
49
  label: string;
50
50
  value: T;
@@ -64,12 +64,13 @@ type SelectProps<T> = {
64
64
  label?: string;
65
65
  immediate?: boolean;
66
66
  direction?: 'vertical' | 'horizontal';
67
+ gap?: number;
67
68
  maxVisible?: number;
68
69
  paginatorStyle?: PaginatorStyle;
69
70
  wrap?: boolean;
70
71
  render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
71
72
  };
72
- 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;
73
74
 
74
75
  type MultiSelectRenderProps<T> = {
75
76
  option: SelectOption<T>;
@@ -85,12 +86,13 @@ type MultiSelectProps<T> = {
85
86
  onHighlight?: (value: T) => void;
86
87
  label?: string;
87
88
  direction?: 'vertical' | 'horizontal';
89
+ gap?: number;
88
90
  maxVisible?: number;
89
91
  paginatorStyle?: PaginatorStyle;
90
92
  wrap?: boolean;
91
93
  render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
92
94
  };
93
- 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;
94
96
 
95
97
  type ConfirmProps = {
96
98
  message: string;
@@ -114,12 +116,13 @@ type AutocompleteProps<T> = {
114
116
  label?: string;
115
117
  placeholder?: string;
116
118
  filter?: (query: string, option: SelectOption<T>) => boolean;
119
+ gap?: number;
117
120
  maxVisible?: number;
118
121
  paginatorStyle?: PaginatorStyle;
119
122
  wrap?: boolean;
120
123
  render?: (props: AutocompleteRenderProps<T>) => React__default.ReactNode;
121
124
  };
122
- 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;
123
126
 
124
127
  type VirtualListRenderProps<T> = {
125
128
  item: T;
@@ -127,6 +130,7 @@ type VirtualListRenderProps<T> = {
127
130
  };
128
131
  type VirtualListBase<T> = {
129
132
  items: T[];
133
+ gap?: number;
130
134
  maxVisible?: number;
131
135
  paginatorStyle?: PaginatorStyle;
132
136
  render: (props: VirtualListRenderProps<T>) => React__default.ReactNode;
@@ -138,7 +142,7 @@ type VirtualListProps<T> = VirtualListBase<T> & ({
138
142
  highlightIndex?: never;
139
143
  scrollOffset?: number;
140
144
  });
141
- 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;
142
146
 
143
147
  type ViewportRenderProps<T> = {
144
148
  item: T;
@@ -149,9 +153,10 @@ type ViewportProps<T> = {
149
153
  items: T[];
150
154
  maxVisible: number;
151
155
  showLineNumbers?: boolean;
156
+ gap?: number;
152
157
  paginatorStyle?: PaginatorStyle;
153
158
  render?: (props: ViewportRenderProps<T>) => React__default.ReactNode;
154
159
  };
155
- 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;
156
161
 
157
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,108 +3,14 @@ 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
- import { useState as useState2 } from "react";
11
- import { Box as Box3, Text as Text2 } from "ink";
12
-
13
- // src/ui/VirtualList.tsx
14
- import React, { useState } from "react";
15
- import { Box as Box2 } from "ink";
16
-
17
- // src/ui/Paginator.tsx
11
+ import { useState } from "react";
18
12
  import { Box, Text } from "ink";
19
- import { jsx, jsxs } from "react/jsx-runtime";
20
- function Paginator({ total, offset, visible, style = "arrows", position }) {
21
- if (total <= visible) return null;
22
- const hasAbove = offset > 0;
23
- const hasBelow = offset + visible < total;
24
- if (style === "arrows") {
25
- if (position === "above" && hasAbove) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191" });
26
- if (position === "below" && hasBelow) return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2193" });
27
- return null;
28
- }
29
- if (style === "counter") {
30
- if (position === "above") return null;
31
- return /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
32
- offset + 1,
33
- "\u2013",
34
- Math.min(offset + visible, total),
35
- " of ",
36
- total
37
- ] });
38
- }
39
- const thumbSize = Math.max(1, Math.round(visible / total * visible));
40
- const maxThumbOffset = visible - thumbSize;
41
- const maxScrollOffset = total - visible;
42
- const thumbOffset = maxScrollOffset === 0 ? 0 : Math.round(offset / maxScrollOffset * maxThumbOffset);
43
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: Array.from({ length: visible }, (_, i) => {
44
- const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
45
- return /* @__PURE__ */ jsx(Text, { dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
46
- }) });
47
- }
48
-
49
- // src/ui/VirtualList.tsx
50
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
51
- function VirtualList({
52
- items,
53
- highlightIndex,
54
- scrollOffset: controlledOffset,
55
- maxVisible,
56
- paginatorStyle = "arrows",
57
- render
58
- }) {
59
- const [internalOffset, setInternalOffset] = useState(0);
60
- if (maxVisible == null || items.length <= maxVisible) {
61
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx2(React.Fragment, { children: render({ item, index }) }, index)) });
62
- }
63
- const maxOffset = Math.max(0, items.length - maxVisible);
64
- let offset;
65
- if (controlledOffset != null) {
66
- offset = Math.min(controlledOffset, maxOffset);
67
- } else {
68
- offset = Math.min(internalOffset, maxOffset);
69
- if (highlightIndex != null && highlightIndex >= 0) {
70
- if (highlightIndex < offset) {
71
- offset = highlightIndex;
72
- } else if (highlightIndex >= offset + maxVisible) {
73
- offset = highlightIndex - maxVisible + 1;
74
- }
75
- }
76
- if (offset !== internalOffset) {
77
- setInternalOffset(offset);
78
- }
79
- }
80
- const visible = items.slice(offset, offset + maxVisible);
81
- const paginatorProps = {
82
- total: items.length,
83
- offset,
84
- visible: maxVisible,
85
- style: paginatorStyle
86
- };
87
- if (paginatorStyle === "scrollbar") {
88
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", gap: 1, children: [
89
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: visible.map((item, i) => {
90
- const index = offset + i;
91
- return /* @__PURE__ */ jsx2(React.Fragment, { children: render({ item, index }) }, index);
92
- }) }),
93
- /* @__PURE__ */ jsx2(Paginator, { ...paginatorProps })
94
- ] });
95
- }
96
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
97
- /* @__PURE__ */ jsx2(Paginator, { ...paginatorProps, position: "above" }),
98
- visible.map((item, i) => {
99
- const index = offset + i;
100
- return /* @__PURE__ */ jsx2(React.Fragment, { children: render({ item, index }) }, index);
101
- }),
102
- /* @__PURE__ */ jsx2(Paginator, { ...paginatorProps, position: "below" })
103
- ] });
104
- }
105
-
106
- // src/ui/CommandPalette.tsx
107
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
13
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
108
14
  var EMPTY_KEY = {
109
15
  upArrow: false,
110
16
  downArrow: false,
@@ -137,12 +43,14 @@ function fuzzyMatch(name, query) {
137
43
  }
138
44
  return qi === lowerQuery.length;
139
45
  }
140
- function Inner({ onClose, maxVisible, paginatorStyle, render }) {
46
+ function Inner({ onClose, render }) {
141
47
  const focus = useFocus();
142
- const [query, setQuery] = useState2("");
143
- const [selectedIndex, setSelectedIndex] = useState2(0);
48
+ const theme = useTheme();
49
+ const [query, setQuery] = useState("");
50
+ const [selectedIndex, setSelectedIndex] = useState(0);
144
51
  const registry = useKeybindingRegistry();
145
- 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));
146
54
  const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
147
55
  const onSelect = (cmd) => {
148
56
  cmd.handler("", EMPTY_KEY);
@@ -156,8 +64,8 @@ function Inner({ onClose, maxVisible, paginatorStyle, render }) {
156
64
  const cmd = filtered[clampedIndex];
157
65
  if (cmd) onSelect(cmd);
158
66
  },
159
- up: () => setSelectedIndex((i) => Math.max(0, i - 1)),
160
- 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),
161
69
  backspace: () => {
162
70
  setQuery((q) => q.slice(0, -1));
163
71
  setSelectedIndex(0);
@@ -174,37 +82,56 @@ function Inner({ onClose, maxVisible, paginatorStyle, render }) {
174
82
  }
175
83
  );
176
84
  if (render) {
177
- return /* @__PURE__ */ jsx3(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
85
+ return /* @__PURE__ */ jsx(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
178
86
  }
179
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", width: 40, children: [
180
- /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, children: [
181
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "> " }),
182
- /* @__PURE__ */ jsx3(Text2, { children: query }),
183
- /* @__PURE__ */ jsx3(Text2, { inverse: true, children: " " })
87
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
88
+ query.length > 0 && /* @__PURE__ */ jsxs(Text, { children: [
89
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
90
+ /* @__PURE__ */ jsx(Text, { children: query }),
91
+ /* @__PURE__ */ jsx(Text, { inverse: true, children: " " })
184
92
  ] }),
185
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "No commands found" }) }) : /* @__PURE__ */ jsx3(
186
- VirtualList,
187
- {
188
- items: filtered,
189
- highlightIndex: clampedIndex,
190
- maxVisible,
191
- paginatorStyle,
192
- render: ({ item: cmd, index }) => /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", paddingX: 1, children: [
193
- /* @__PURE__ */ jsx3(Text2, { inverse: index === clampedIndex, children: cmd.name }),
194
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: cmd.key })
195
- ] }, `${cmd.nodeId}-${cmd.key}`)
196
- }
197
- ) })
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
+ }) })
198
106
  ] });
199
107
  }
200
- function CommandPalette({ onClose, maxVisible, paginatorStyle, render }) {
201
- return /* @__PURE__ */ jsx3(FocusTrap, { children: /* @__PURE__ */ jsx3(Inner, { onClose, maxVisible, paginatorStyle, 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 }) });
202
129
  }
203
130
 
204
131
  // src/ui/TextInput.tsx
205
132
  import { useReducer, useRef } from "react";
206
- import { Text as Text3 } from "ink";
207
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
133
+ import { Text as Text2 } from "ink";
134
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
208
135
  function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
209
136
  const focus = useFocus();
210
137
  const cursorRef = useRef(value.length);
@@ -261,28 +188,149 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
261
188
  const cursorChar = value[cursor] ?? " ";
262
189
  const after = value.slice(cursor + 1);
263
190
  if (render) {
264
- return /* @__PURE__ */ jsx4(Fragment2, { children: render({ value, focused: focus.focused, before, cursorChar, after }) });
191
+ return /* @__PURE__ */ jsx2(Fragment2, { children: render({ value, focused: focus.focused, before, cursorChar, after }) });
265
192
  }
266
193
  const displayValue = value.length > 0 ? value : placeholder ?? "";
267
194
  const isPlaceholder = value.length === 0;
268
- const prefix = label != null ? `${label} ` : "";
269
195
  if (focus.focused) {
270
- return /* @__PURE__ */ jsxs4(Text3, { children: [
271
- prefix,
196
+ return /* @__PURE__ */ jsxs2(Text2, { children: [
197
+ label != null && /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
198
+ label,
199
+ " "
200
+ ] }),
272
201
  before,
273
- /* @__PURE__ */ jsx4(Text3, { inverse: true, children: cursorChar }),
202
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
274
203
  after
275
204
  ] });
276
205
  }
277
- return /* @__PURE__ */ jsxs4(Text3, { children: [
278
- prefix,
279
- /* @__PURE__ */ jsx4(Text3, { 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
280
212
  ] });
281
213
  }
282
214
 
283
215
  // src/ui/Select.tsx
284
- import React4, { useEffect, useState as useState3 } from "react";
216
+ import React3, { useEffect as useEffect2, useState as useState3 } from "react";
285
217
  import { Box as Box4, Text as Text4 } from "ink";
218
+
219
+ // src/ui/VirtualList.tsx
220
+ import React2, { useEffect, useRef as useRef2, useState as useState2 } from "react";
221
+ import { Box as Box3 } from "ink";
222
+
223
+ // src/ui/Paginator.tsx
224
+ import { Box as Box2, Text as Text3 } from "ink";
225
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
226
+ function Paginator({ total, offset, visible, gap = 0, style = "arrows", position }) {
227
+ const theme = useTheme();
228
+ if (style === "none" || total <= visible) return null;
229
+ const hasAbove = offset > 0;
230
+ const hasBelow = offset + visible < total;
231
+ if (style === "arrows") {
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" });
234
+ return null;
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
+ }
246
+ if (style === "counter") {
247
+ if (position === "above") return null;
248
+ return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
249
+ offset + 1,
250
+ "\u2013",
251
+ Math.min(offset + visible, total),
252
+ " of ",
253
+ total
254
+ ] });
255
+ }
256
+ const totalLines = visible + gap * (visible - 1);
257
+ const thumbSize = Math.max(1, Math.round(visible / total * totalLines));
258
+ const maxThumbOffset = totalLines - thumbSize;
259
+ const maxScrollOffset = total - visible;
260
+ const thumbOffset = maxScrollOffset === 0 ? 0 : Math.round(offset / maxScrollOffset * maxThumbOffset);
261
+ return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: Array.from({ length: totalLines }, (_, i) => {
262
+ const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
263
+ return /* @__PURE__ */ jsx3(Text3, { color: isThumb ? theme.accentColor : void 0, dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
264
+ }) });
265
+ }
266
+
267
+ // src/ui/VirtualList.tsx
268
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
269
+ function VirtualList({
270
+ items,
271
+ highlightIndex,
272
+ scrollOffset: controlledOffset,
273
+ gap = 0,
274
+ maxVisible,
275
+ paginatorStyle = "dots",
276
+ render
277
+ }) {
278
+ const [internalOffset, setInternalOffset] = useState2(0);
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
+ }
294
+ }
295
+ }
296
+ }
297
+ offsetRef.current = offset;
298
+ useEffect(() => {
299
+ if (windowed && offsetRef.current !== internalOffset) {
300
+ setInternalOffset(offsetRef.current);
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)) });
305
+ }
306
+ const visible = items.slice(offset, offset + maxVisible);
307
+ const paginatorProps = {
308
+ total: items.length,
309
+ offset,
310
+ visible: maxVisible,
311
+ gap,
312
+ style: paginatorStyle
313
+ };
314
+ if (paginatorStyle === "scrollbar") {
315
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "row", gap: 1, children: [
316
+ /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", gap, flexGrow: 1, children: visible.map((item, i) => {
317
+ const index = offset + i;
318
+ return /* @__PURE__ */ jsx4(React2.Fragment, { children: render({ item, index }) }, index);
319
+ }) }),
320
+ /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps })
321
+ ] });
322
+ }
323
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", gap, children: [
324
+ /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "above" }),
325
+ visible.map((item, i) => {
326
+ const index = offset + i;
327
+ return /* @__PURE__ */ jsx4(React2.Fragment, { children: render({ item, index }) }, index);
328
+ }),
329
+ /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "below" })
330
+ ] });
331
+ }
332
+
333
+ // src/ui/Select.tsx
286
334
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
287
335
  function Select({
288
336
  options,
@@ -293,6 +341,7 @@ function Select({
293
341
  label,
294
342
  immediate,
295
343
  direction = "vertical",
344
+ gap,
296
345
  maxVisible,
297
346
  paginatorStyle,
298
347
  wrap = true,
@@ -307,9 +356,10 @@ function Select({
307
356
  seen.add(key);
308
357
  }
309
358
  const focus = useFocus();
359
+ const theme = useTheme();
310
360
  const [highlightIndex, setHighlightIndex] = useState3(0);
311
361
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
312
- useEffect(() => {
362
+ useEffect2(() => {
313
363
  if (safeIndex !== highlightIndex) {
314
364
  setHighlightIndex(Math.max(0, safeIndex));
315
365
  }
@@ -347,19 +397,17 @@ function Select({
347
397
  if (render) {
348
398
  return render({ option, focused: focus.focused, highlighted, selected });
349
399
  }
350
- return /* @__PURE__ */ jsxs5(Text4, { dimColor: !focus.focused, children: [
351
- highlighted ? ">" : " ",
352
- " ",
353
- option.label
354
- ] }, 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));
355
402
  };
356
403
  return /* @__PURE__ */ jsxs5(Box4, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
357
404
  label != null && /* @__PURE__ */ jsx5(Text4, { children: label }),
358
- 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(
359
406
  VirtualList,
360
407
  {
361
408
  items: options,
362
409
  highlightIndex: safeIndex,
410
+ gap,
363
411
  maxVisible,
364
412
  paginatorStyle,
365
413
  render: renderOption
@@ -369,7 +417,7 @@ function Select({
369
417
  }
370
418
 
371
419
  // src/ui/MultiSelect.tsx
372
- import React5, { useEffect as useEffect2, useState as useState4 } from "react";
420
+ import React4, { useEffect as useEffect3, useState as useState4 } from "react";
373
421
  import { Box as Box5, Text as Text5 } from "ink";
374
422
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
375
423
  function MultiSelect({
@@ -380,6 +428,7 @@ function MultiSelect({
380
428
  onHighlight,
381
429
  label,
382
430
  direction = "vertical",
431
+ gap,
383
432
  maxVisible,
384
433
  paginatorStyle,
385
434
  wrap = true,
@@ -394,9 +443,10 @@ function MultiSelect({
394
443
  seen.add(key);
395
444
  }
396
445
  const focus = useFocus();
446
+ const theme = useTheme();
397
447
  const [highlightIndex, setHighlightIndex] = useState4(0);
398
448
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
399
- useEffect2(() => {
449
+ useEffect3(() => {
400
450
  if (safeIndex !== highlightIndex) {
401
451
  setHighlightIndex(Math.max(0, safeIndex));
402
452
  }
@@ -430,21 +480,21 @@ function MultiSelect({
430
480
  if (render) {
431
481
  return render({ option, focused: focus.focused, highlighted, selected });
432
482
  }
433
- return /* @__PURE__ */ jsxs6(Text5, { dimColor: !focus.focused, children: [
434
- highlighted ? ">" : " ",
435
- " [",
436
- selected ? "x" : " ",
437
- "] ",
438
- option.label
439
- ] }, 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));
440
489
  };
441
490
  return /* @__PURE__ */ jsxs6(Box5, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
442
491
  label != null && /* @__PURE__ */ jsx6(Text5, { children: label }),
443
- 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(
444
493
  VirtualList,
445
494
  {
446
495
  items: options,
447
496
  highlightIndex: safeIndex,
497
+ gap,
448
498
  maxVisible,
449
499
  paginatorStyle,
450
500
  render: renderOption
@@ -466,14 +516,17 @@ function Confirm({ message, defaultValue = true, onSubmit }) {
466
516
  const hint = defaultValue ? "Y/n" : "y/N";
467
517
  return /* @__PURE__ */ jsxs7(Text6, { dimColor: !focus.focused, children: [
468
518
  message,
469
- " (",
470
- hint,
471
- ")"
519
+ " ",
520
+ /* @__PURE__ */ jsxs7(Text6, { dimColor: true, children: [
521
+ "(",
522
+ hint,
523
+ ")"
524
+ ] })
472
525
  ] });
473
526
  }
474
527
 
475
528
  // src/ui/Autocomplete.tsx
476
- import { useEffect as useEffect3, useMemo, 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";
477
530
  import { Box as Box6, Text as Text7 } from "ink";
478
531
  import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
479
532
  function defaultFilter(query, option) {
@@ -492,6 +545,7 @@ function Autocomplete({
492
545
  label,
493
546
  placeholder,
494
547
  filter = defaultFilter,
548
+ gap,
495
549
  maxVisible,
496
550
  paginatorStyle,
497
551
  wrap = true,
@@ -506,16 +560,17 @@ function Autocomplete({
506
560
  seen.add(key);
507
561
  }
508
562
  const focus = useFocus();
563
+ const theme = useTheme();
509
564
  const [query, setQuery] = useState5("");
510
565
  const [highlightIndex, setHighlightIndex] = useState5(0);
511
- const cursorRef = useRef2(0);
566
+ const cursorRef = useRef3(0);
512
567
  const [, forceRender] = useReducer2((c) => c + 1, 0);
513
568
  const filtered = useMemo(
514
569
  () => query.length === 0 ? options : options.filter((opt) => filter(query, opt)),
515
570
  [options, query, filter]
516
571
  );
517
572
  const safeIndex = filtered.length === 0 ? -1 : Math.min(highlightIndex, filtered.length - 1);
518
- useEffect3(() => {
573
+ useEffect4(() => {
519
574
  if (safeIndex >= 0 && safeIndex !== highlightIndex) {
520
575
  setHighlightIndex(safeIndex);
521
576
  }
@@ -590,15 +645,20 @@ function Autocomplete({
590
645
  const before = query.slice(0, cursor);
591
646
  const cursorChar = query[cursor] ?? " ";
592
647
  const after = query.slice(cursor + 1);
593
- const prefix = label != null ? `${label} ` : "";
594
- return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
648
+ return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", gap: 1, children: [
595
649
  focus.focused ? /* @__PURE__ */ jsxs8(Text7, { children: [
596
- prefix,
650
+ label != null && /* @__PURE__ */ jsxs8(Text7, { bold: true, children: [
651
+ label,
652
+ " "
653
+ ] }),
597
654
  before,
598
655
  /* @__PURE__ */ jsx7(Text7, { inverse: true, children: cursorChar }),
599
656
  after
600
657
  ] }) : /* @__PURE__ */ jsxs8(Text7, { dimColor: true, children: [
601
- prefix,
658
+ label != null && /* @__PURE__ */ jsxs8(Text7, { children: [
659
+ label,
660
+ " "
661
+ ] }),
602
662
  query.length > 0 ? query : placeholder ?? ""
603
663
  ] }),
604
664
  /* @__PURE__ */ jsx7(
@@ -606,6 +666,7 @@ function Autocomplete({
606
666
  {
607
667
  items: filtered,
608
668
  highlightIndex: safeIndex,
669
+ gap,
609
670
  maxVisible,
610
671
  paginatorStyle,
611
672
  render: ({ item: option, index }) => {
@@ -614,11 +675,8 @@ function Autocomplete({
614
675
  if (render) {
615
676
  return render({ option, focused: focus.focused, highlighted, selected });
616
677
  }
617
- return /* @__PURE__ */ jsxs8(Text7, { dimColor: !focus.focused, children: [
618
- highlighted ? ">" : " ",
619
- " ",
620
- option.label
621
- ] });
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 }) }) });
622
680
  }
623
681
  }
624
682
  )
@@ -629,7 +687,7 @@ function Autocomplete({
629
687
  import { useState as useState6 } from "react";
630
688
  import { Text as Text8 } from "ink";
631
689
  import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
632
- function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }) {
690
+ function Viewport({ items, maxVisible, showLineNumbers, gap, paginatorStyle, render }) {
633
691
  const focus = useFocus();
634
692
  const [scrollOffset, setScrollOffset] = useState6(0);
635
693
  const maxOffset = Math.max(0, items.length - maxVisible);
@@ -652,6 +710,7 @@ function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }
652
710
  {
653
711
  items,
654
712
  scrollOffset,
713
+ gap,
655
714
  maxVisible,
656
715
  paginatorStyle,
657
716
  render: ({ item, index }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",