giggles 0.3.3 → 0.3.5
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/ui/index.d.ts +85 -4
- package/dist/ui/index.js +425 -69
- package/package.json +1 -1
package/dist/ui/index.d.ts
CHANGED
|
@@ -3,6 +3,17 @@ import React__default from 'react';
|
|
|
3
3
|
import { R as RegisteredKeybinding } from '../types-Dmw9TKt4.js';
|
|
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
|
+
|
|
6
17
|
type CommandPaletteRenderProps = {
|
|
7
18
|
query: string;
|
|
8
19
|
filtered: RegisteredKeybinding[];
|
|
@@ -11,9 +22,11 @@ type CommandPaletteRenderProps = {
|
|
|
11
22
|
};
|
|
12
23
|
type CommandPaletteProps = {
|
|
13
24
|
onClose: () => void;
|
|
25
|
+
maxVisible?: number;
|
|
26
|
+
paginatorStyle?: PaginatorStyle;
|
|
14
27
|
render?: (props: CommandPaletteRenderProps) => React__default.ReactNode;
|
|
15
28
|
};
|
|
16
|
-
declare function CommandPalette({ onClose, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
declare function CommandPalette({ onClose, maxVisible, paginatorStyle, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
|
|
17
30
|
|
|
18
31
|
type TextInputRenderProps = {
|
|
19
32
|
value: string;
|
|
@@ -51,9 +64,12 @@ type SelectProps<T> = {
|
|
|
51
64
|
label?: string;
|
|
52
65
|
immediate?: boolean;
|
|
53
66
|
direction?: 'vertical' | 'horizontal';
|
|
67
|
+
maxVisible?: number;
|
|
68
|
+
paginatorStyle?: PaginatorStyle;
|
|
69
|
+
wrap?: boolean;
|
|
54
70
|
render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
|
|
55
71
|
};
|
|
56
|
-
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
72
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
57
73
|
|
|
58
74
|
type MultiSelectRenderProps<T> = {
|
|
59
75
|
option: SelectOption<T>;
|
|
@@ -69,8 +85,73 @@ type MultiSelectProps<T> = {
|
|
|
69
85
|
onHighlight?: (value: T) => void;
|
|
70
86
|
label?: string;
|
|
71
87
|
direction?: 'vertical' | 'horizontal';
|
|
88
|
+
maxVisible?: number;
|
|
89
|
+
paginatorStyle?: PaginatorStyle;
|
|
90
|
+
wrap?: boolean;
|
|
72
91
|
render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
|
|
73
92
|
};
|
|
74
|
-
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
93
|
+
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
94
|
+
|
|
95
|
+
type ConfirmProps = {
|
|
96
|
+
message: string;
|
|
97
|
+
defaultValue?: boolean;
|
|
98
|
+
onSubmit: (value: boolean) => void;
|
|
99
|
+
};
|
|
100
|
+
declare function Confirm({ message, defaultValue, onSubmit }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
101
|
+
|
|
102
|
+
type AutocompleteRenderProps<T> = {
|
|
103
|
+
option: SelectOption<T>;
|
|
104
|
+
focused: boolean;
|
|
105
|
+
highlighted: boolean;
|
|
106
|
+
selected: boolean;
|
|
107
|
+
};
|
|
108
|
+
type AutocompleteProps<T> = {
|
|
109
|
+
options: SelectOption<T>[];
|
|
110
|
+
value: T;
|
|
111
|
+
onChange: (value: T) => void;
|
|
112
|
+
onSubmit?: (value: T) => void;
|
|
113
|
+
onHighlight?: (value: T) => void;
|
|
114
|
+
label?: string;
|
|
115
|
+
placeholder?: string;
|
|
116
|
+
filter?: (query: string, option: SelectOption<T>) => boolean;
|
|
117
|
+
maxVisible?: number;
|
|
118
|
+
paginatorStyle?: PaginatorStyle;
|
|
119
|
+
wrap?: boolean;
|
|
120
|
+
render?: (props: AutocompleteRenderProps<T>) => React__default.ReactNode;
|
|
121
|
+
};
|
|
122
|
+
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
123
|
+
|
|
124
|
+
type VirtualListRenderProps<T> = {
|
|
125
|
+
item: T;
|
|
126
|
+
index: number;
|
|
127
|
+
};
|
|
128
|
+
type VirtualListBase<T> = {
|
|
129
|
+
items: T[];
|
|
130
|
+
maxVisible?: number;
|
|
131
|
+
paginatorStyle?: PaginatorStyle;
|
|
132
|
+
render: (props: VirtualListRenderProps<T>) => React__default.ReactNode;
|
|
133
|
+
};
|
|
134
|
+
type VirtualListProps<T> = VirtualListBase<T> & ({
|
|
135
|
+
highlightIndex?: number;
|
|
136
|
+
scrollOffset?: never;
|
|
137
|
+
} | {
|
|
138
|
+
highlightIndex?: never;
|
|
139
|
+
scrollOffset?: number;
|
|
140
|
+
});
|
|
141
|
+
declare function VirtualList<T>({ items, highlightIndex, scrollOffset: controlledOffset, maxVisible, paginatorStyle, render }: VirtualListProps<T>): react_jsx_runtime.JSX.Element;
|
|
142
|
+
|
|
143
|
+
type ViewportRenderProps<T> = {
|
|
144
|
+
item: T;
|
|
145
|
+
index: number;
|
|
146
|
+
focused: boolean;
|
|
147
|
+
};
|
|
148
|
+
type ViewportProps<T> = {
|
|
149
|
+
items: T[];
|
|
150
|
+
maxVisible: number;
|
|
151
|
+
showLineNumbers?: boolean;
|
|
152
|
+
paginatorStyle?: PaginatorStyle;
|
|
153
|
+
render?: (props: ViewportRenderProps<T>) => React__default.ReactNode;
|
|
154
|
+
};
|
|
155
|
+
declare function Viewport<T>({ items, maxVisible, showLineNumbers, paginatorStyle, render }: ViewportProps<T>): react_jsx_runtime.JSX.Element;
|
|
75
156
|
|
|
76
|
-
export { CommandPalette, type CommandPaletteRenderProps, MultiSelect, type MultiSelectRenderProps, Select, type SelectOption, type SelectRenderProps, TextInput, type TextInputRenderProps };
|
|
157
|
+
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
|
@@ -7,9 +7,104 @@ import {
|
|
|
7
7
|
} from "../chunk-OYQZHF73.js";
|
|
8
8
|
|
|
9
9
|
// src/ui/CommandPalette.tsx
|
|
10
|
-
import { useState } from "react";
|
|
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
18
|
import { Box, Text } from "ink";
|
|
12
|
-
import {
|
|
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
108
|
var EMPTY_KEY = {
|
|
14
109
|
upArrow: false,
|
|
15
110
|
downArrow: false,
|
|
@@ -42,10 +137,10 @@ function fuzzyMatch(name, query) {
|
|
|
42
137
|
}
|
|
43
138
|
return qi === lowerQuery.length;
|
|
44
139
|
}
|
|
45
|
-
function Inner({ onClose, render }) {
|
|
140
|
+
function Inner({ onClose, maxVisible, paginatorStyle, render }) {
|
|
46
141
|
const focus = useFocus();
|
|
47
|
-
const [query, setQuery] =
|
|
48
|
-
const [selectedIndex, setSelectedIndex] =
|
|
142
|
+
const [query, setQuery] = useState2("");
|
|
143
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
49
144
|
const registry = useKeybindingRegistry();
|
|
50
145
|
const filtered = registry.all.filter((cmd) => fuzzyMatch(cmd.name, query));
|
|
51
146
|
const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
|
|
@@ -79,28 +174,37 @@ function Inner({ onClose, render }) {
|
|
|
79
174
|
}
|
|
80
175
|
);
|
|
81
176
|
if (render) {
|
|
82
|
-
return /* @__PURE__ */
|
|
177
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
|
|
83
178
|
}
|
|
84
|
-
return /* @__PURE__ */
|
|
85
|
-
/* @__PURE__ */
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
-
/* @__PURE__ */
|
|
88
|
-
/* @__PURE__ */
|
|
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: " " })
|
|
89
184
|
] }),
|
|
90
|
-
/* @__PURE__ */
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
) })
|
|
94
198
|
] });
|
|
95
199
|
}
|
|
96
|
-
function CommandPalette({ onClose, render }) {
|
|
97
|
-
return /* @__PURE__ */
|
|
200
|
+
function CommandPalette({ onClose, maxVisible, paginatorStyle, render }) {
|
|
201
|
+
return /* @__PURE__ */ jsx3(FocusTrap, { children: /* @__PURE__ */ jsx3(Inner, { onClose, maxVisible, paginatorStyle, render }) });
|
|
98
202
|
}
|
|
99
203
|
|
|
100
204
|
// src/ui/TextInput.tsx
|
|
101
205
|
import { useReducer, useRef } from "react";
|
|
102
|
-
import { Text as
|
|
103
|
-
import { Fragment as Fragment2, jsx as
|
|
206
|
+
import { Text as Text3 } from "ink";
|
|
207
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
104
208
|
function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
105
209
|
const focus = useFocus();
|
|
106
210
|
const cursorRef = useRef(value.length);
|
|
@@ -157,29 +261,29 @@ function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
|
157
261
|
const cursorChar = value[cursor] ?? " ";
|
|
158
262
|
const after = value.slice(cursor + 1);
|
|
159
263
|
if (render) {
|
|
160
|
-
return /* @__PURE__ */
|
|
264
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: render({ value, focused: focus.focused, before, cursorChar, after }) });
|
|
161
265
|
}
|
|
162
266
|
const displayValue = value.length > 0 ? value : placeholder ?? "";
|
|
163
267
|
const isPlaceholder = value.length === 0;
|
|
164
268
|
const prefix = label != null ? `${label} ` : "";
|
|
165
269
|
if (focus.focused) {
|
|
166
|
-
return /* @__PURE__ */
|
|
270
|
+
return /* @__PURE__ */ jsxs4(Text3, { children: [
|
|
167
271
|
prefix,
|
|
168
272
|
before,
|
|
169
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ jsx4(Text3, { inverse: true, children: cursorChar }),
|
|
170
274
|
after
|
|
171
275
|
] });
|
|
172
276
|
}
|
|
173
|
-
return /* @__PURE__ */
|
|
277
|
+
return /* @__PURE__ */ jsxs4(Text3, { children: [
|
|
174
278
|
prefix,
|
|
175
|
-
/* @__PURE__ */
|
|
279
|
+
/* @__PURE__ */ jsx4(Text3, { dimColor: isPlaceholder, children: displayValue })
|
|
176
280
|
] });
|
|
177
281
|
}
|
|
178
282
|
|
|
179
283
|
// src/ui/Select.tsx
|
|
180
|
-
import
|
|
181
|
-
import { Box as
|
|
182
|
-
import { jsx as
|
|
284
|
+
import React4, { useEffect, useState as useState3 } from "react";
|
|
285
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
286
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
183
287
|
function Select({
|
|
184
288
|
options,
|
|
185
289
|
value,
|
|
@@ -189,6 +293,9 @@ function Select({
|
|
|
189
293
|
label,
|
|
190
294
|
immediate,
|
|
191
295
|
direction = "vertical",
|
|
296
|
+
maxVisible,
|
|
297
|
+
paginatorStyle,
|
|
298
|
+
wrap = true,
|
|
192
299
|
render
|
|
193
300
|
}) {
|
|
194
301
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -200,14 +307,16 @@ function Select({
|
|
|
200
307
|
seen.add(key);
|
|
201
308
|
}
|
|
202
309
|
const focus = useFocus();
|
|
203
|
-
const [highlightIndex, setHighlightIndex] =
|
|
310
|
+
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
204
311
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (safeIndex !== highlightIndex) {
|
|
314
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
315
|
+
}
|
|
316
|
+
}, [safeIndex, highlightIndex]);
|
|
208
317
|
const moveHighlight = (delta) => {
|
|
209
318
|
if (options.length === 0) return;
|
|
210
|
-
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
319
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
211
320
|
if (next2 !== safeIndex) {
|
|
212
321
|
setHighlightIndex(next2);
|
|
213
322
|
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
@@ -232,27 +341,37 @@ function Select({
|
|
|
232
341
|
}
|
|
233
342
|
});
|
|
234
343
|
const isHorizontal = direction === "horizontal";
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
344
|
+
const renderOption = ({ item: option, index }) => {
|
|
345
|
+
const highlighted = index === safeIndex;
|
|
346
|
+
const selected = option.value === value;
|
|
347
|
+
if (render) {
|
|
348
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
349
|
+
}
|
|
350
|
+
return /* @__PURE__ */ jsxs5(Text4, { dimColor: !focus.focused, children: [
|
|
351
|
+
highlighted ? ">" : " ",
|
|
352
|
+
" ",
|
|
353
|
+
option.label
|
|
354
|
+
] }, String(option.value));
|
|
355
|
+
};
|
|
356
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
357
|
+
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(
|
|
359
|
+
VirtualList,
|
|
360
|
+
{
|
|
361
|
+
items: options,
|
|
362
|
+
highlightIndex: safeIndex,
|
|
363
|
+
maxVisible,
|
|
364
|
+
paginatorStyle,
|
|
365
|
+
render: renderOption
|
|
242
366
|
}
|
|
243
|
-
|
|
244
|
-
highlighted ? ">" : " ",
|
|
245
|
-
" ",
|
|
246
|
-
option.label
|
|
247
|
-
] }, String(option.value));
|
|
248
|
-
})
|
|
367
|
+
)
|
|
249
368
|
] });
|
|
250
369
|
}
|
|
251
370
|
|
|
252
371
|
// src/ui/MultiSelect.tsx
|
|
253
|
-
import
|
|
254
|
-
import { Box as
|
|
255
|
-
import { jsx as
|
|
372
|
+
import React5, { useEffect as useEffect2, useState as useState4 } from "react";
|
|
373
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
374
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
256
375
|
function MultiSelect({
|
|
257
376
|
options,
|
|
258
377
|
value,
|
|
@@ -261,6 +380,9 @@ function MultiSelect({
|
|
|
261
380
|
onHighlight,
|
|
262
381
|
label,
|
|
263
382
|
direction = "vertical",
|
|
383
|
+
maxVisible,
|
|
384
|
+
paginatorStyle,
|
|
385
|
+
wrap = true,
|
|
264
386
|
render
|
|
265
387
|
}) {
|
|
266
388
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -272,14 +394,16 @@ function MultiSelect({
|
|
|
272
394
|
seen.add(key);
|
|
273
395
|
}
|
|
274
396
|
const focus = useFocus();
|
|
275
|
-
const [highlightIndex, setHighlightIndex] =
|
|
397
|
+
const [highlightIndex, setHighlightIndex] = useState4(0);
|
|
276
398
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
399
|
+
useEffect2(() => {
|
|
400
|
+
if (safeIndex !== highlightIndex) {
|
|
401
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
402
|
+
}
|
|
403
|
+
}, [safeIndex, highlightIndex]);
|
|
280
404
|
const moveHighlight = (delta) => {
|
|
281
405
|
if (options.length === 0) return;
|
|
282
|
-
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
406
|
+
const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
283
407
|
if (next2 !== safeIndex) {
|
|
284
408
|
setHighlightIndex(next2);
|
|
285
409
|
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
@@ -300,27 +424,259 @@ function MultiSelect({
|
|
|
300
424
|
...onSubmit && { enter: () => onSubmit(value) }
|
|
301
425
|
});
|
|
302
426
|
const isHorizontal = direction === "horizontal";
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
427
|
+
const renderOption = ({ item: option, index }) => {
|
|
428
|
+
const highlighted = index === safeIndex;
|
|
429
|
+
const selected = value.includes(option.value);
|
|
430
|
+
if (render) {
|
|
431
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
432
|
+
}
|
|
433
|
+
return /* @__PURE__ */ jsxs6(Text5, { dimColor: !focus.focused, children: [
|
|
434
|
+
highlighted ? ">" : " ",
|
|
435
|
+
" [",
|
|
436
|
+
selected ? "x" : " ",
|
|
437
|
+
"] ",
|
|
438
|
+
option.label
|
|
439
|
+
] }, String(option.value));
|
|
440
|
+
};
|
|
441
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
442
|
+
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(
|
|
444
|
+
VirtualList,
|
|
445
|
+
{
|
|
446
|
+
items: options,
|
|
447
|
+
highlightIndex: safeIndex,
|
|
448
|
+
maxVisible,
|
|
449
|
+
paginatorStyle,
|
|
450
|
+
render: renderOption
|
|
310
451
|
}
|
|
311
|
-
|
|
312
|
-
highlighted ? ">" : " ",
|
|
313
|
-
" [",
|
|
314
|
-
selected ? "x" : " ",
|
|
315
|
-
"] ",
|
|
316
|
-
option.label
|
|
317
|
-
] }, String(option.value));
|
|
318
|
-
})
|
|
452
|
+
)
|
|
319
453
|
] });
|
|
320
454
|
}
|
|
455
|
+
|
|
456
|
+
// src/ui/Confirm.tsx
|
|
457
|
+
import { Text as Text6 } from "ink";
|
|
458
|
+
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
459
|
+
function Confirm({ message, defaultValue = true, onSubmit }) {
|
|
460
|
+
const focus = useFocus();
|
|
461
|
+
useKeybindings(focus, {
|
|
462
|
+
y: () => onSubmit(true),
|
|
463
|
+
n: () => onSubmit(false),
|
|
464
|
+
enter: () => onSubmit(defaultValue)
|
|
465
|
+
});
|
|
466
|
+
const hint = defaultValue ? "Y/n" : "y/N";
|
|
467
|
+
return /* @__PURE__ */ jsxs7(Text6, { dimColor: !focus.focused, children: [
|
|
468
|
+
message,
|
|
469
|
+
" (",
|
|
470
|
+
hint,
|
|
471
|
+
")"
|
|
472
|
+
] });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/ui/Autocomplete.tsx
|
|
476
|
+
import { useEffect as useEffect3, useMemo, useReducer as useReducer2, useRef as useRef2, useState as useState5 } from "react";
|
|
477
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
478
|
+
import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
479
|
+
function defaultFilter(query, option) {
|
|
480
|
+
const caseSensitive = query !== query.toLowerCase();
|
|
481
|
+
if (caseSensitive) {
|
|
482
|
+
return option.label.includes(query);
|
|
483
|
+
}
|
|
484
|
+
return option.label.toLowerCase().includes(query);
|
|
485
|
+
}
|
|
486
|
+
function Autocomplete({
|
|
487
|
+
options,
|
|
488
|
+
value,
|
|
489
|
+
onChange,
|
|
490
|
+
onSubmit,
|
|
491
|
+
onHighlight,
|
|
492
|
+
label,
|
|
493
|
+
placeholder,
|
|
494
|
+
filter = defaultFilter,
|
|
495
|
+
maxVisible,
|
|
496
|
+
paginatorStyle,
|
|
497
|
+
wrap = true,
|
|
498
|
+
render
|
|
499
|
+
}) {
|
|
500
|
+
const seen = /* @__PURE__ */ new Set();
|
|
501
|
+
for (const opt of options) {
|
|
502
|
+
const key = String(opt.value);
|
|
503
|
+
if (seen.has(key)) {
|
|
504
|
+
throw new GigglesError("Autocomplete options must have unique values");
|
|
505
|
+
}
|
|
506
|
+
seen.add(key);
|
|
507
|
+
}
|
|
508
|
+
const focus = useFocus();
|
|
509
|
+
const [query, setQuery] = useState5("");
|
|
510
|
+
const [highlightIndex, setHighlightIndex] = useState5(0);
|
|
511
|
+
const cursorRef = useRef2(0);
|
|
512
|
+
const [, forceRender] = useReducer2((c) => c + 1, 0);
|
|
513
|
+
const filtered = useMemo(
|
|
514
|
+
() => query.length === 0 ? options : options.filter((opt) => filter(query, opt)),
|
|
515
|
+
[options, query, filter]
|
|
516
|
+
);
|
|
517
|
+
const safeIndex = filtered.length === 0 ? -1 : Math.min(highlightIndex, filtered.length - 1);
|
|
518
|
+
useEffect3(() => {
|
|
519
|
+
if (safeIndex >= 0 && safeIndex !== highlightIndex) {
|
|
520
|
+
setHighlightIndex(safeIndex);
|
|
521
|
+
}
|
|
522
|
+
}, [safeIndex, highlightIndex]);
|
|
523
|
+
const cursor = Math.min(cursorRef.current, query.length);
|
|
524
|
+
cursorRef.current = cursor;
|
|
525
|
+
const moveHighlight = (delta) => {
|
|
526
|
+
if (filtered.length === 0) return;
|
|
527
|
+
const next = wrap ? (safeIndex + delta + filtered.length) % filtered.length : Math.max(0, Math.min(filtered.length - 1, safeIndex + delta));
|
|
528
|
+
if (next !== safeIndex) {
|
|
529
|
+
setHighlightIndex(next);
|
|
530
|
+
onHighlight == null ? void 0 : onHighlight(filtered[next].value);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const updateQuery = (newQuery) => {
|
|
534
|
+
setQuery(newQuery);
|
|
535
|
+
setHighlightIndex(0);
|
|
536
|
+
};
|
|
537
|
+
useKeybindings(
|
|
538
|
+
focus,
|
|
539
|
+
{
|
|
540
|
+
up: () => moveHighlight(-1),
|
|
541
|
+
down: () => moveHighlight(1),
|
|
542
|
+
left: () => {
|
|
543
|
+
cursorRef.current = Math.max(0, cursorRef.current - 1);
|
|
544
|
+
forceRender();
|
|
545
|
+
},
|
|
546
|
+
right: () => {
|
|
547
|
+
cursorRef.current = Math.min(query.length, cursorRef.current + 1);
|
|
548
|
+
forceRender();
|
|
549
|
+
},
|
|
550
|
+
home: () => {
|
|
551
|
+
cursorRef.current = 0;
|
|
552
|
+
forceRender();
|
|
553
|
+
},
|
|
554
|
+
end: () => {
|
|
555
|
+
cursorRef.current = query.length;
|
|
556
|
+
forceRender();
|
|
557
|
+
},
|
|
558
|
+
backspace: () => {
|
|
559
|
+
const c = cursorRef.current;
|
|
560
|
+
if (c > 0) {
|
|
561
|
+
cursorRef.current = c - 1;
|
|
562
|
+
updateQuery(query.slice(0, c - 1) + query.slice(c));
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
delete: () => {
|
|
566
|
+
const c = cursorRef.current;
|
|
567
|
+
if (c < query.length) {
|
|
568
|
+
updateQuery(query.slice(0, c) + query.slice(c + 1));
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
enter: () => {
|
|
572
|
+
if (filtered.length === 0) return;
|
|
573
|
+
const selected = filtered[safeIndex].value;
|
|
574
|
+
onChange(selected);
|
|
575
|
+
onSubmit == null ? void 0 : onSubmit(selected);
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
capture: true,
|
|
580
|
+
passthrough: ["tab", "shift+tab", "escape"],
|
|
581
|
+
onKeypress: (input, key) => {
|
|
582
|
+
if (input.length === 1 && !key.ctrl && !key.return && !key.escape && !key.tab) {
|
|
583
|
+
const c = cursorRef.current;
|
|
584
|
+
cursorRef.current = c + 1;
|
|
585
|
+
updateQuery(query.slice(0, c) + input + query.slice(c));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
);
|
|
590
|
+
const before = query.slice(0, cursor);
|
|
591
|
+
const cursorChar = query[cursor] ?? " ";
|
|
592
|
+
const after = query.slice(cursor + 1);
|
|
593
|
+
const prefix = label != null ? `${label} ` : "";
|
|
594
|
+
return /* @__PURE__ */ jsxs8(Box6, { flexDirection: "column", children: [
|
|
595
|
+
focus.focused ? /* @__PURE__ */ jsxs8(Text7, { children: [
|
|
596
|
+
prefix,
|
|
597
|
+
before,
|
|
598
|
+
/* @__PURE__ */ jsx7(Text7, { inverse: true, children: cursorChar }),
|
|
599
|
+
after
|
|
600
|
+
] }) : /* @__PURE__ */ jsxs8(Text7, { dimColor: true, children: [
|
|
601
|
+
prefix,
|
|
602
|
+
query.length > 0 ? query : placeholder ?? ""
|
|
603
|
+
] }),
|
|
604
|
+
/* @__PURE__ */ jsx7(
|
|
605
|
+
VirtualList,
|
|
606
|
+
{
|
|
607
|
+
items: filtered,
|
|
608
|
+
highlightIndex: safeIndex,
|
|
609
|
+
maxVisible,
|
|
610
|
+
paginatorStyle,
|
|
611
|
+
render: ({ item: option, index }) => {
|
|
612
|
+
const highlighted = index === safeIndex;
|
|
613
|
+
const selected = option.value === value;
|
|
614
|
+
if (render) {
|
|
615
|
+
return render({ option, focused: focus.focused, highlighted, selected });
|
|
616
|
+
}
|
|
617
|
+
return /* @__PURE__ */ jsxs8(Text7, { dimColor: !focus.focused, children: [
|
|
618
|
+
highlighted ? ">" : " ",
|
|
619
|
+
" ",
|
|
620
|
+
option.label
|
|
621
|
+
] });
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
)
|
|
625
|
+
] });
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/ui/Viewport.tsx
|
|
629
|
+
import { useState as useState6 } from "react";
|
|
630
|
+
import { Text as Text8 } from "ink";
|
|
631
|
+
import { jsx as jsx8, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
632
|
+
function Viewport({ items, maxVisible, showLineNumbers, paginatorStyle, render }) {
|
|
633
|
+
const focus = useFocus();
|
|
634
|
+
const [scrollOffset, setScrollOffset] = useState6(0);
|
|
635
|
+
const maxOffset = Math.max(0, items.length - maxVisible);
|
|
636
|
+
const scroll = (delta) => {
|
|
637
|
+
setScrollOffset((prev) => Math.max(0, Math.min(maxOffset, prev + delta)));
|
|
638
|
+
};
|
|
639
|
+
useKeybindings(focus, {
|
|
640
|
+
j: () => scroll(1),
|
|
641
|
+
k: () => scroll(-1),
|
|
642
|
+
down: () => scroll(1),
|
|
643
|
+
up: () => scroll(-1),
|
|
644
|
+
pagedown: () => scroll(maxVisible),
|
|
645
|
+
pageup: () => scroll(-maxVisible),
|
|
646
|
+
g: () => setScrollOffset(0),
|
|
647
|
+
G: () => setScrollOffset(maxOffset)
|
|
648
|
+
});
|
|
649
|
+
const gutterWidth = showLineNumbers ? String(items.length).length + 1 : 0;
|
|
650
|
+
return /* @__PURE__ */ jsx8(
|
|
651
|
+
VirtualList,
|
|
652
|
+
{
|
|
653
|
+
items,
|
|
654
|
+
scrollOffset,
|
|
655
|
+
maxVisible,
|
|
656
|
+
paginatorStyle,
|
|
657
|
+
render: ({ item, index }) => {
|
|
658
|
+
if (render) {
|
|
659
|
+
return render({ item, index, focused: focus.focused });
|
|
660
|
+
}
|
|
661
|
+
return /* @__PURE__ */ jsxs9(Text8, { dimColor: !focus.focused, children: [
|
|
662
|
+
showLineNumbers && /* @__PURE__ */ jsxs9(Text8, { dimColor: true, children: [
|
|
663
|
+
String(index + 1).padStart(gutterWidth - 1),
|
|
664
|
+
" "
|
|
665
|
+
] }),
|
|
666
|
+
String(item)
|
|
667
|
+
] });
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
321
672
|
export {
|
|
673
|
+
Autocomplete,
|
|
322
674
|
CommandPalette,
|
|
675
|
+
Confirm,
|
|
323
676
|
MultiSelect,
|
|
677
|
+
Paginator,
|
|
324
678
|
Select,
|
|
325
|
-
TextInput
|
|
679
|
+
TextInput,
|
|
680
|
+
Viewport,
|
|
681
|
+
VirtualList
|
|
326
682
|
};
|