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.
- package/dist/{chunk-OYQZHF73.js → chunk-FEQYHTG7.js} +27 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.js +9 -5
- package/dist/ui/index.d.ts +18 -11
- package/dist/ui/index.js +166 -89
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
};
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -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
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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",
|
|
85
|
-
/* @__PURE__ */ jsxs(
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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, {
|
|
196
|
-
if (position === "below" && hasBelow) return /* @__PURE__ */ jsx3(Text3, {
|
|
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
|
|
210
|
-
const
|
|
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:
|
|
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 = "
|
|
275
|
+
paginatorStyle = "dots",
|
|
227
276
|
render
|
|
228
277
|
}) {
|
|
229
278
|
const [internalOffset, setInternalOffset] = useState2(0);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
304
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
566
|
+
const cursorRef = useRef3(0);
|
|
499
567
|
const [, forceRender] = useReducer2((c) => c + 1, 0);
|
|
500
|
-
const filtered =
|
|
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
|
-
|
|
503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
600
|
-
|
|
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 }) => {
|