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
- return direction === "vertical" ? {
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-ClgDW3fy.js';
5
- export { b as KeyHandler } from './types-ClgDW3fy.js';
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
@@ -14,7 +14,7 @@ import {
14
14
  useFocusState,
15
15
  useKeybindingRegistry,
16
16
  useKeybindings
17
- } from "./chunk-4LED4GXQ.js";
17
+ } from "./chunk-OYQZHF73.js";
18
18
 
19
19
  // src/core/GigglesProvider.tsx
20
20
  import { jsx } from "react/jsx-runtime";
@@ -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 = {
@@ -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-ClgDW3fy.js';
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
- export { CommandPalette, type CommandPaletteRenderProps };
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-4LED4GXQ.js";
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",