giggles 0.3.4 → 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.
@@ -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;
@@ -32,17 +45,6 @@ type TextInputProps = {
32
45
  };
33
46
  declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
34
47
 
35
- type PaginatorStyle = 'arrows' | 'scrollbar' | 'counter';
36
- type PaginatorProps = {
37
- total: number;
38
- offset: number;
39
- visible: number;
40
- style?: PaginatorStyle;
41
- position?: 'above' | 'below';
42
- };
43
-
44
- declare function Paginator({ total, offset, visible, style, position }: PaginatorProps): react_jsx_runtime.JSX.Element | null;
45
-
46
48
  type SelectOption<T> = {
47
49
  label: string;
48
50
  value: T;
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 { Fragment, jsx, jsxs } from "react/jsx-runtime";
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] = useState("");
48
- const [selectedIndex, setSelectedIndex] = useState(0);
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__ */ jsx(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
177
+ return /* @__PURE__ */ jsx3(Fragment, { children: render({ query, filtered, selectedIndex: clampedIndex, onSelect }) });
83
178
  }
84
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", width: 40, children: [
85
- /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
86
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "> " }),
87
- /* @__PURE__ */ jsx(Text, { children: query }),
88
- /* @__PURE__ */ jsx(Text, { inverse: true, children: " " })
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__ */ jsx(Box, { flexDirection: "column", children: filtered.length === 0 ? /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No commands found" }) }) : filtered.map((cmd, i) => /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [
91
- /* @__PURE__ */ jsx(Text, { inverse: i === clampedIndex, children: cmd.name }),
92
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: cmd.key })
93
- ] }, `${cmd.nodeId}-${cmd.key}`)) })
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__ */ jsx(FocusTrap, { children: /* @__PURE__ */ jsx(Inner, { onClose, render }) });
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 Text2 } from "ink";
103
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
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,123 +261,28 @@ 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__ */ jsx2(Fragment2, { children: render({ value, focused: focus.focused, before, cursorChar, after }) });
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__ */ jsxs2(Text2, { children: [
270
+ return /* @__PURE__ */ jsxs4(Text3, { children: [
167
271
  prefix,
168
272
  before,
169
- /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
273
+ /* @__PURE__ */ jsx4(Text3, { inverse: true, children: cursorChar }),
170
274
  after
171
275
  ] });
172
276
  }
173
- return /* @__PURE__ */ jsxs2(Text2, { children: [
277
+ return /* @__PURE__ */ jsxs4(Text3, { children: [
174
278
  prefix,
175
- /* @__PURE__ */ jsx2(Text2, { dimColor: isPlaceholder, children: displayValue })
279
+ /* @__PURE__ */ jsx4(Text3, { dimColor: isPlaceholder, children: displayValue })
176
280
  ] });
177
281
  }
178
282
 
179
283
  // src/ui/Select.tsx
180
- import React4, { useState as useState3 } from "react";
284
+ import React4, { useEffect, useState as useState3 } from "react";
181
285
  import { Box as Box4, Text as Text4 } from "ink";
182
-
183
- // src/ui/VirtualList.tsx
184
- import React3, { useState as useState2 } from "react";
185
- import { Box as Box3 } from "ink";
186
-
187
- // src/ui/Paginator.tsx
188
- import { Box as Box2, Text as Text3 } from "ink";
189
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
190
- function Paginator({ total, offset, visible, style = "arrows", position }) {
191
- if (total <= visible) return null;
192
- const hasAbove = offset > 0;
193
- const hasBelow = offset + visible < total;
194
- if (style === "arrows") {
195
- if (position === "above" && hasAbove) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2191" });
196
- if (position === "below" && hasBelow) return /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2193" });
197
- return null;
198
- }
199
- if (style === "counter") {
200
- if (position === "above") return null;
201
- return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
202
- offset + 1,
203
- "\u2013",
204
- Math.min(offset + visible, total),
205
- " of ",
206
- total
207
- ] });
208
- }
209
- const thumbSize = Math.max(1, Math.round(visible / total * visible));
210
- const maxThumbOffset = visible - thumbSize;
211
- const maxScrollOffset = total - visible;
212
- const thumbOffset = maxScrollOffset === 0 ? 0 : Math.round(offset / maxScrollOffset * maxThumbOffset);
213
- return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: Array.from({ length: visible }, (_, i) => {
214
- const isThumb = i >= thumbOffset && i < thumbOffset + thumbSize;
215
- return /* @__PURE__ */ jsx3(Text3, { dimColor: !isThumb, children: isThumb ? "\u2588" : "\u2502" }, i);
216
- }) });
217
- }
218
-
219
- // src/ui/VirtualList.tsx
220
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
221
- function VirtualList({
222
- items,
223
- highlightIndex,
224
- scrollOffset: controlledOffset,
225
- maxVisible,
226
- paginatorStyle = "arrows",
227
- render
228
- }) {
229
- const [internalOffset, setInternalOffset] = useState2(0);
230
- if (maxVisible == null || items.length <= maxVisible) {
231
- return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index)) });
232
- }
233
- const maxOffset = Math.max(0, items.length - maxVisible);
234
- let offset;
235
- if (controlledOffset != null) {
236
- offset = Math.min(controlledOffset, maxOffset);
237
- } else {
238
- offset = Math.min(internalOffset, maxOffset);
239
- if (highlightIndex != null && highlightIndex >= 0) {
240
- if (highlightIndex < offset) {
241
- offset = highlightIndex;
242
- } else if (highlightIndex >= offset + maxVisible) {
243
- offset = highlightIndex - maxVisible + 1;
244
- }
245
- }
246
- if (offset !== internalOffset) {
247
- setInternalOffset(offset);
248
- }
249
- }
250
- const visible = items.slice(offset, offset + maxVisible);
251
- const paginatorProps = {
252
- total: items.length,
253
- offset,
254
- visible: maxVisible,
255
- style: paginatorStyle
256
- };
257
- if (paginatorStyle === "scrollbar") {
258
- return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "row", gap: 1, children: [
259
- /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: visible.map((item, i) => {
260
- const index = offset + i;
261
- return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
262
- }) }),
263
- /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps })
264
- ] });
265
- }
266
- return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
267
- /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "above" }),
268
- visible.map((item, i) => {
269
- const index = offset + i;
270
- return /* @__PURE__ */ jsx4(React3.Fragment, { children: render({ item, index }) }, index);
271
- }),
272
- /* @__PURE__ */ jsx4(Paginator, { ...paginatorProps, position: "below" })
273
- ] });
274
- }
275
-
276
- // src/ui/Select.tsx
277
286
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
278
287
  function Select({
279
288
  options,
@@ -300,9 +309,11 @@ function Select({
300
309
  const focus = useFocus();
301
310
  const [highlightIndex, setHighlightIndex] = useState3(0);
302
311
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
303
- if (safeIndex !== highlightIndex) {
304
- setHighlightIndex(Math.max(0, safeIndex));
305
- }
312
+ useEffect(() => {
313
+ if (safeIndex !== highlightIndex) {
314
+ setHighlightIndex(Math.max(0, safeIndex));
315
+ }
316
+ }, [safeIndex, highlightIndex]);
306
317
  const moveHighlight = (delta) => {
307
318
  if (options.length === 0) return;
308
319
  const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
@@ -358,7 +369,7 @@ function Select({
358
369
  }
359
370
 
360
371
  // src/ui/MultiSelect.tsx
361
- import React5, { useState as useState4 } from "react";
372
+ import React5, { useEffect as useEffect2, useState as useState4 } from "react";
362
373
  import { Box as Box5, Text as Text5 } from "ink";
363
374
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
364
375
  function MultiSelect({
@@ -385,9 +396,11 @@ function MultiSelect({
385
396
  const focus = useFocus();
386
397
  const [highlightIndex, setHighlightIndex] = useState4(0);
387
398
  const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
388
- if (safeIndex !== highlightIndex) {
389
- setHighlightIndex(Math.max(0, safeIndex));
390
- }
399
+ useEffect2(() => {
400
+ if (safeIndex !== highlightIndex) {
401
+ setHighlightIndex(Math.max(0, safeIndex));
402
+ }
403
+ }, [safeIndex, highlightIndex]);
391
404
  const moveHighlight = (delta) => {
392
405
  if (options.length === 0) return;
393
406
  const next2 = wrap ? (safeIndex + delta + options.length) % options.length : Math.max(0, Math.min(options.length - 1, safeIndex + delta));
@@ -460,7 +473,7 @@ function Confirm({ message, defaultValue = true, onSubmit }) {
460
473
  }
461
474
 
462
475
  // src/ui/Autocomplete.tsx
463
- import { useReducer as useReducer2, useRef as useRef2, useState as useState5 } from "react";
476
+ import { useEffect as useEffect3, useMemo, useReducer as useReducer2, useRef as useRef2, useState as useState5 } from "react";
464
477
  import { Box as Box6, Text as Text7 } from "ink";
465
478
  import { jsx as jsx7, jsxs as jsxs8 } from "react/jsx-runtime";
466
479
  function defaultFilter(query, option) {
@@ -497,11 +510,16 @@ function Autocomplete({
497
510
  const [highlightIndex, setHighlightIndex] = useState5(0);
498
511
  const cursorRef = useRef2(0);
499
512
  const [, forceRender] = useReducer2((c) => c + 1, 0);
500
- const filtered = query.length === 0 ? options : options.filter((opt) => filter(query, opt));
513
+ const filtered = useMemo(
514
+ () => query.length === 0 ? options : options.filter((opt) => filter(query, opt)),
515
+ [options, query, filter]
516
+ );
501
517
  const safeIndex = filtered.length === 0 ? -1 : Math.min(highlightIndex, filtered.length - 1);
502
- if (safeIndex >= 0 && safeIndex !== highlightIndex) {
503
- setHighlightIndex(safeIndex);
504
- }
518
+ useEffect3(() => {
519
+ if (safeIndex >= 0 && safeIndex !== highlightIndex) {
520
+ setHighlightIndex(safeIndex);
521
+ }
522
+ }, [safeIndex, highlightIndex]);
505
523
  const cursor = Math.min(cursorRef.current, query.length);
506
524
  cursorRef.current = cursor;
507
525
  const moveHighlight = (delta) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "giggles",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",