giggles 0.3.1 → 0.3.3
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.
|
@@ -27,6 +27,7 @@ var InputProvider = ({ children }) => {
|
|
|
27
27
|
bindings: new Map(entries),
|
|
28
28
|
capture: (options == null ? void 0 : options.capture) ?? false,
|
|
29
29
|
onKeypress: options == null ? void 0 : options.onKeypress,
|
|
30
|
+
passthrough: (options == null ? void 0 : options.passthrough) ? new Set(options.passthrough) : void 0,
|
|
30
31
|
layer: options == null ? void 0 : options.layer
|
|
31
32
|
};
|
|
32
33
|
bindingsRef.current.set(nodeId, registration);
|
|
@@ -281,7 +282,9 @@ function normalizeKey(input, key) {
|
|
|
281
282
|
if (key.rightArrow) return "right";
|
|
282
283
|
if (key.return) return "enter";
|
|
283
284
|
if (key.escape) return "escape";
|
|
285
|
+
if (key.tab && key.shift) return "shift+tab";
|
|
284
286
|
if (key.tab) return "tab";
|
|
287
|
+
if (input === "\x1B[3~") return "delete";
|
|
285
288
|
if (key.backspace || key.delete) return "backspace";
|
|
286
289
|
if (key.pageUp) return "pageup";
|
|
287
290
|
if (key.pageDown) return "pagedown";
|
|
@@ -299,6 +302,7 @@ function InputRouter({ children }) {
|
|
|
299
302
|
const { getFocusedId, getActiveBranchPath } = useFocusContext();
|
|
300
303
|
const { getNodeBindings, getTrapNodeId, getAllBindings } = useInputContext();
|
|
301
304
|
useInput((input, key) => {
|
|
305
|
+
var _a;
|
|
302
306
|
const focusedId = getFocusedId();
|
|
303
307
|
if (!focusedId) return;
|
|
304
308
|
const path = getActiveBranchPath();
|
|
@@ -314,6 +318,9 @@ function InputRouter({ children }) {
|
|
|
314
318
|
return;
|
|
315
319
|
}
|
|
316
320
|
if (nodeBindings.capture && nodeBindings.onKeypress) {
|
|
321
|
+
if ((_a = nodeBindings.passthrough) == null ? void 0 : _a.has(keyName)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
317
324
|
nodeBindings.onKeypress(input, key);
|
|
318
325
|
return;
|
|
319
326
|
}
|
|
@@ -396,7 +403,7 @@ function FocusGroup({
|
|
|
396
403
|
if (!navigable) return {};
|
|
397
404
|
const next = () => navigateSibling("next", wrap);
|
|
398
405
|
const prev = () => navigateSibling("prev", wrap);
|
|
399
|
-
|
|
406
|
+
const base = direction === "vertical" ? {
|
|
400
407
|
j: next,
|
|
401
408
|
k: prev,
|
|
402
409
|
down: next,
|
|
@@ -407,6 +414,7 @@ function FocusGroup({
|
|
|
407
414
|
right: next,
|
|
408
415
|
left: prev
|
|
409
416
|
};
|
|
417
|
+
return { ...base, tab: next, "shift+tab": prev };
|
|
410
418
|
}, [navigable, direction, wrap, navigateSibling]);
|
|
411
419
|
const mergedBindings = useMemo(
|
|
412
420
|
() => ({ ...navigationKeys, ...customBindings }),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React$1 from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
|
-
import { K as Keybindings, a as KeybindingOptions, R as RegisteredKeybinding } from './types-
|
|
5
|
-
export { b as KeyHandler } from './types-
|
|
4
|
+
import { K as Keybindings, a as KeybindingOptions, R as RegisteredKeybinding } from './types-Dmw9TKt4.js';
|
|
5
|
+
export { b as KeyHandler } from './types-Dmw9TKt4.js';
|
|
6
6
|
export { Key } from 'ink';
|
|
7
7
|
|
|
8
8
|
declare class GigglesError extends Error {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Key } from 'ink';
|
|
2
2
|
|
|
3
3
|
type KeyHandler = (input: string, key: Key) => void;
|
|
4
|
-
type SpecialKey = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'tab' | 'backspace' | 'pageup' | 'pagedown' | 'home' | 'end';
|
|
4
|
+
type SpecialKey = 'up' | 'down' | 'left' | 'right' | 'enter' | 'escape' | 'tab' | 'backspace' | 'delete' | 'pageup' | 'pagedown' | 'home' | 'end';
|
|
5
5
|
type KeyName = SpecialKey | (string & {});
|
|
6
6
|
type KeybindingDefinition = KeyHandler | {
|
|
7
7
|
action: KeyHandler;
|
|
@@ -12,6 +12,7 @@ type Keybindings = Partial<Record<KeyName, KeybindingDefinition>>;
|
|
|
12
12
|
type KeybindingOptions = {
|
|
13
13
|
capture?: boolean;
|
|
14
14
|
onKeypress?: (input: string, key: Key) => void;
|
|
15
|
+
passthrough?: string[];
|
|
15
16
|
layer?: string;
|
|
16
17
|
};
|
|
17
18
|
type RegisteredKeybinding = {
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
|
-
import { R as RegisteredKeybinding } from '../types-
|
|
3
|
+
import { R as RegisteredKeybinding } from '../types-Dmw9TKt4.js';
|
|
4
4
|
import 'ink';
|
|
5
5
|
|
|
6
6
|
type CommandPaletteRenderProps = {
|
|
@@ -15,4 +15,62 @@ type CommandPaletteProps = {
|
|
|
15
15
|
};
|
|
16
16
|
declare function CommandPalette({ onClose, render }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type TextInputRenderProps = {
|
|
19
|
+
value: string;
|
|
20
|
+
focused: boolean;
|
|
21
|
+
before: string;
|
|
22
|
+
cursorChar: string;
|
|
23
|
+
after: string;
|
|
24
|
+
};
|
|
25
|
+
type TextInputProps = {
|
|
26
|
+
label?: string;
|
|
27
|
+
value: string;
|
|
28
|
+
onChange: (value: string) => void;
|
|
29
|
+
onSubmit?: (value: string) => void;
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
render?: (props: TextInputRenderProps) => React__default.ReactNode;
|
|
32
|
+
};
|
|
33
|
+
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
34
|
+
|
|
35
|
+
type SelectOption<T> = {
|
|
36
|
+
label: string;
|
|
37
|
+
value: T;
|
|
38
|
+
};
|
|
39
|
+
type SelectRenderProps<T> = {
|
|
40
|
+
option: SelectOption<T>;
|
|
41
|
+
focused: boolean;
|
|
42
|
+
highlighted: boolean;
|
|
43
|
+
selected: boolean;
|
|
44
|
+
};
|
|
45
|
+
type SelectProps<T> = {
|
|
46
|
+
options: SelectOption<T>[];
|
|
47
|
+
value: T;
|
|
48
|
+
onChange: (value: T) => void;
|
|
49
|
+
onSubmit?: (value: T) => void;
|
|
50
|
+
onHighlight?: (value: T) => void;
|
|
51
|
+
label?: string;
|
|
52
|
+
immediate?: boolean;
|
|
53
|
+
direction?: 'vertical' | 'horizontal';
|
|
54
|
+
render?: (props: SelectRenderProps<T>) => React__default.ReactNode;
|
|
55
|
+
};
|
|
56
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
57
|
+
|
|
58
|
+
type MultiSelectRenderProps<T> = {
|
|
59
|
+
option: SelectOption<T>;
|
|
60
|
+
focused: boolean;
|
|
61
|
+
highlighted: boolean;
|
|
62
|
+
selected: boolean;
|
|
63
|
+
};
|
|
64
|
+
type MultiSelectProps<T> = {
|
|
65
|
+
options: SelectOption<T>[];
|
|
66
|
+
value: T[];
|
|
67
|
+
onChange: (value: T[]) => void;
|
|
68
|
+
onSubmit?: (value: T[]) => void;
|
|
69
|
+
onHighlight?: (value: T) => void;
|
|
70
|
+
label?: string;
|
|
71
|
+
direction?: 'vertical' | 'horizontal';
|
|
72
|
+
render?: (props: MultiSelectRenderProps<T>) => React__default.ReactNode;
|
|
73
|
+
};
|
|
74
|
+
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
75
|
+
|
|
76
|
+
export { CommandPalette, type CommandPaletteRenderProps, MultiSelect, type MultiSelectRenderProps, Select, type SelectOption, type SelectRenderProps, TextInput, type TextInputRenderProps };
|
package/dist/ui/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FocusTrap,
|
|
3
|
+
GigglesError,
|
|
3
4
|
useFocus,
|
|
4
5
|
useKeybindingRegistry,
|
|
5
6
|
useKeybindings
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-OYQZHF73.js";
|
|
7
8
|
|
|
8
9
|
// src/ui/CommandPalette.tsx
|
|
9
10
|
import { useState } from "react";
|
|
@@ -95,6 +96,231 @@ function Inner({ onClose, render }) {
|
|
|
95
96
|
function CommandPalette({ onClose, render }) {
|
|
96
97
|
return /* @__PURE__ */ jsx(FocusTrap, { children: /* @__PURE__ */ jsx(Inner, { onClose, render }) });
|
|
97
98
|
}
|
|
99
|
+
|
|
100
|
+
// src/ui/TextInput.tsx
|
|
101
|
+
import { useReducer, useRef } from "react";
|
|
102
|
+
import { Text as Text2 } from "ink";
|
|
103
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
104
|
+
function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
105
|
+
const focus = useFocus();
|
|
106
|
+
const cursorRef = useRef(value.length);
|
|
107
|
+
const [, forceRender] = useReducer((c) => c + 1, 0);
|
|
108
|
+
const cursor = Math.min(cursorRef.current, value.length);
|
|
109
|
+
cursorRef.current = cursor;
|
|
110
|
+
useKeybindings(
|
|
111
|
+
focus,
|
|
112
|
+
{
|
|
113
|
+
left: () => {
|
|
114
|
+
cursorRef.current = Math.max(0, cursorRef.current - 1);
|
|
115
|
+
forceRender();
|
|
116
|
+
},
|
|
117
|
+
right: () => {
|
|
118
|
+
cursorRef.current = Math.min(value.length, cursorRef.current + 1);
|
|
119
|
+
forceRender();
|
|
120
|
+
},
|
|
121
|
+
home: () => {
|
|
122
|
+
cursorRef.current = 0;
|
|
123
|
+
forceRender();
|
|
124
|
+
},
|
|
125
|
+
end: () => {
|
|
126
|
+
cursorRef.current = value.length;
|
|
127
|
+
forceRender();
|
|
128
|
+
},
|
|
129
|
+
backspace: () => {
|
|
130
|
+
const c = cursorRef.current;
|
|
131
|
+
if (c > 0) {
|
|
132
|
+
cursorRef.current = c - 1;
|
|
133
|
+
onChange(value.slice(0, c - 1) + value.slice(c));
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
delete: () => {
|
|
137
|
+
const c = cursorRef.current;
|
|
138
|
+
if (c < value.length) {
|
|
139
|
+
onChange(value.slice(0, c) + value.slice(c + 1));
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
...onSubmit && { enter: () => onSubmit(value) }
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
capture: true,
|
|
146
|
+
passthrough: ["tab", "shift+tab", "enter", "escape"],
|
|
147
|
+
onKeypress: (input, key) => {
|
|
148
|
+
if (input.length === 1 && !key.ctrl && !key.return && !key.escape && !key.tab) {
|
|
149
|
+
const c = cursorRef.current;
|
|
150
|
+
cursorRef.current = c + 1;
|
|
151
|
+
onChange(value.slice(0, c) + input + value.slice(c));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
const before = value.slice(0, cursor);
|
|
157
|
+
const cursorChar = value[cursor] ?? " ";
|
|
158
|
+
const after = value.slice(cursor + 1);
|
|
159
|
+
if (render) {
|
|
160
|
+
return /* @__PURE__ */ jsx2(Fragment2, { children: render({ value, focused: focus.focused, before, cursorChar, after }) });
|
|
161
|
+
}
|
|
162
|
+
const displayValue = value.length > 0 ? value : placeholder ?? "";
|
|
163
|
+
const isPlaceholder = value.length === 0;
|
|
164
|
+
const prefix = label != null ? `${label} ` : "";
|
|
165
|
+
if (focus.focused) {
|
|
166
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
167
|
+
prefix,
|
|
168
|
+
before,
|
|
169
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
|
|
170
|
+
after
|
|
171
|
+
] });
|
|
172
|
+
}
|
|
173
|
+
return /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
174
|
+
prefix,
|
|
175
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: isPlaceholder, children: displayValue })
|
|
176
|
+
] });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/ui/Select.tsx
|
|
180
|
+
import React3, { useState as useState2 } from "react";
|
|
181
|
+
import { Box as Box2, Text as Text3 } from "ink";
|
|
182
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
183
|
+
function Select({
|
|
184
|
+
options,
|
|
185
|
+
value,
|
|
186
|
+
onChange,
|
|
187
|
+
onSubmit,
|
|
188
|
+
onHighlight,
|
|
189
|
+
label,
|
|
190
|
+
immediate,
|
|
191
|
+
direction = "vertical",
|
|
192
|
+
render
|
|
193
|
+
}) {
|
|
194
|
+
const seen = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const opt of options) {
|
|
196
|
+
const key = String(opt.value);
|
|
197
|
+
if (seen.has(key)) {
|
|
198
|
+
throw new GigglesError("Select options must have unique values");
|
|
199
|
+
}
|
|
200
|
+
seen.add(key);
|
|
201
|
+
}
|
|
202
|
+
const focus = useFocus();
|
|
203
|
+
const [highlightIndex, setHighlightIndex] = useState2(0);
|
|
204
|
+
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
205
|
+
if (safeIndex !== highlightIndex) {
|
|
206
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
207
|
+
}
|
|
208
|
+
const moveHighlight = (delta) => {
|
|
209
|
+
if (options.length === 0) return;
|
|
210
|
+
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
211
|
+
if (next2 !== safeIndex) {
|
|
212
|
+
setHighlightIndex(next2);
|
|
213
|
+
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
214
|
+
if (immediate) {
|
|
215
|
+
onChange(options[next2].value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const prev = () => moveHighlight(-1);
|
|
220
|
+
const next = () => moveHighlight(1);
|
|
221
|
+
const navBindings = direction === "vertical" ? { j: next, k: prev, down: next, up: prev } : { l: next, h: prev, right: next, left: prev };
|
|
222
|
+
useKeybindings(focus, {
|
|
223
|
+
...navBindings,
|
|
224
|
+
enter: () => {
|
|
225
|
+
if (options.length === 0) return;
|
|
226
|
+
if (immediate) {
|
|
227
|
+
onSubmit == null ? void 0 : onSubmit(options[safeIndex].value);
|
|
228
|
+
} else {
|
|
229
|
+
onChange(options[safeIndex].value);
|
|
230
|
+
onSubmit == null ? void 0 : onSubmit(options[safeIndex].value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const isHorizontal = direction === "horizontal";
|
|
235
|
+
return /* @__PURE__ */ jsxs3(Box2, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
236
|
+
label != null && /* @__PURE__ */ jsx3(Text3, { children: label }),
|
|
237
|
+
options.map((option, index) => {
|
|
238
|
+
const highlighted = index === safeIndex;
|
|
239
|
+
const selected = option.value === value;
|
|
240
|
+
if (render) {
|
|
241
|
+
return /* @__PURE__ */ jsx3(React3.Fragment, { children: render({ option, focused: focus.focused, highlighted, selected }) }, String(option.value));
|
|
242
|
+
}
|
|
243
|
+
return /* @__PURE__ */ jsxs3(Text3, { dimColor: !focus.focused, children: [
|
|
244
|
+
highlighted ? ">" : " ",
|
|
245
|
+
" ",
|
|
246
|
+
option.label
|
|
247
|
+
] }, String(option.value));
|
|
248
|
+
})
|
|
249
|
+
] });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/ui/MultiSelect.tsx
|
|
253
|
+
import React4, { useState as useState3 } from "react";
|
|
254
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
255
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
256
|
+
function MultiSelect({
|
|
257
|
+
options,
|
|
258
|
+
value,
|
|
259
|
+
onChange,
|
|
260
|
+
onSubmit,
|
|
261
|
+
onHighlight,
|
|
262
|
+
label,
|
|
263
|
+
direction = "vertical",
|
|
264
|
+
render
|
|
265
|
+
}) {
|
|
266
|
+
const seen = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const opt of options) {
|
|
268
|
+
const key = String(opt.value);
|
|
269
|
+
if (seen.has(key)) {
|
|
270
|
+
throw new GigglesError("MultiSelect options must have unique values");
|
|
271
|
+
}
|
|
272
|
+
seen.add(key);
|
|
273
|
+
}
|
|
274
|
+
const focus = useFocus();
|
|
275
|
+
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
276
|
+
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
277
|
+
if (safeIndex !== highlightIndex) {
|
|
278
|
+
setHighlightIndex(Math.max(0, safeIndex));
|
|
279
|
+
}
|
|
280
|
+
const moveHighlight = (delta) => {
|
|
281
|
+
if (options.length === 0) return;
|
|
282
|
+
const next2 = Math.max(0, Math.min(options.length - 1, safeIndex + delta));
|
|
283
|
+
if (next2 !== safeIndex) {
|
|
284
|
+
setHighlightIndex(next2);
|
|
285
|
+
onHighlight == null ? void 0 : onHighlight(options[next2].value);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const toggle = () => {
|
|
289
|
+
if (options.length === 0) return;
|
|
290
|
+
const item = options[safeIndex].value;
|
|
291
|
+
const exists = value.includes(item);
|
|
292
|
+
onChange(exists ? value.filter((v) => v !== item) : [...value, item]);
|
|
293
|
+
};
|
|
294
|
+
const prev = () => moveHighlight(-1);
|
|
295
|
+
const next = () => moveHighlight(1);
|
|
296
|
+
const navBindings = direction === "vertical" ? { j: next, k: prev, down: next, up: prev } : { l: next, h: prev, right: next, left: prev };
|
|
297
|
+
useKeybindings(focus, {
|
|
298
|
+
...navBindings,
|
|
299
|
+
" ": toggle,
|
|
300
|
+
...onSubmit && { enter: () => onSubmit(value) }
|
|
301
|
+
});
|
|
302
|
+
const isHorizontal = direction === "horizontal";
|
|
303
|
+
return /* @__PURE__ */ jsxs4(Box3, { flexDirection: isHorizontal ? "row" : "column", gap: isHorizontal ? 1 : 0, children: [
|
|
304
|
+
label != null && /* @__PURE__ */ jsx4(Text4, { children: label }),
|
|
305
|
+
options.map((option, index) => {
|
|
306
|
+
const highlighted = index === safeIndex;
|
|
307
|
+
const selected = value.includes(option.value);
|
|
308
|
+
if (render) {
|
|
309
|
+
return /* @__PURE__ */ jsx4(React4.Fragment, { children: render({ option, focused: focus.focused, highlighted, selected }) }, String(option.value));
|
|
310
|
+
}
|
|
311
|
+
return /* @__PURE__ */ jsxs4(Text4, { dimColor: !focus.focused, children: [
|
|
312
|
+
highlighted ? ">" : " ",
|
|
313
|
+
" [",
|
|
314
|
+
selected ? "x" : " ",
|
|
315
|
+
"] ",
|
|
316
|
+
option.label
|
|
317
|
+
] }, String(option.value));
|
|
318
|
+
})
|
|
319
|
+
] });
|
|
320
|
+
}
|
|
98
321
|
export {
|
|
99
|
-
CommandPalette
|
|
322
|
+
CommandPalette,
|
|
323
|
+
MultiSelect,
|
|
324
|
+
Select,
|
|
325
|
+
TextInput
|
|
100
326
|
};
|