giggles 0.6.2 → 0.7.0
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-TWXBZE5C.js → chunk-74PBSWEK.js} +61 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +1 -1
- package/dist/ui/index.d.ts +11 -5
- package/dist/ui/index.js +16 -13
- package/package.json +2 -2
|
@@ -76,13 +76,14 @@ function useFocusNode(options) {
|
|
|
76
76
|
const store = useStore();
|
|
77
77
|
const contextParentId = useContext2(ScopeIdContext);
|
|
78
78
|
const parentId = ((_a = options == null ? void 0 : options.parent) == null ? void 0 : _a.id) ?? contextParentId;
|
|
79
|
+
const focusKey = options == null ? void 0 : options.focusKey;
|
|
79
80
|
const subscribe = useMemo(() => store.subscribe.bind(store), [store]);
|
|
80
81
|
useEffect2(() => {
|
|
81
|
-
store.registerNode(id, parentId);
|
|
82
|
+
store.registerNode(id, parentId, focusKey);
|
|
82
83
|
return () => {
|
|
83
84
|
store.unregisterNode(id);
|
|
84
85
|
};
|
|
85
|
-
}, [id, parentId, store]);
|
|
86
|
+
}, [id, parentId, focusKey, store]);
|
|
86
87
|
const hasFocus = useSyncExternalStore2(subscribe, () => store.isFocused(id));
|
|
87
88
|
return { id, hasFocus };
|
|
88
89
|
}
|
|
@@ -128,13 +129,14 @@ function useFocusScope(options) {
|
|
|
128
129
|
const store = useStore();
|
|
129
130
|
const contextParentId = useContext3(ScopeIdContext);
|
|
130
131
|
const parentId = ((_a = options == null ? void 0 : options.parent) == null ? void 0 : _a.id) ?? contextParentId;
|
|
132
|
+
const focusKey = options == null ? void 0 : options.focusKey;
|
|
131
133
|
const subscribe = useMemo2(() => store.subscribe.bind(store), [store]);
|
|
132
134
|
useEffect4(() => {
|
|
133
|
-
store.registerNode(id, parentId);
|
|
135
|
+
store.registerNode(id, parentId, focusKey);
|
|
134
136
|
return () => {
|
|
135
137
|
store.unregisterNode(id);
|
|
136
138
|
};
|
|
137
|
-
}, [id, parentId, store]);
|
|
139
|
+
}, [id, parentId, focusKey, store]);
|
|
138
140
|
const hasFocus = useSyncExternalStore3(subscribe, () => store.isFocused(id));
|
|
139
141
|
const isPassive = useSyncExternalStore3(subscribe, () => store.isPassive(id));
|
|
140
142
|
const next = useCallback(() => store.navigateSibling("next", true, id), [store, id]);
|
|
@@ -143,7 +145,9 @@ function useFocusScope(options) {
|
|
|
143
145
|
const prevShallow = useCallback(() => store.navigateSibling("prev", true, id, true), [store, id]);
|
|
144
146
|
const escape = useCallback(() => store.makePassive(id), [store, id]);
|
|
145
147
|
const drillIn = useCallback(() => store.focusFirstChild(id), [store, id]);
|
|
146
|
-
const
|
|
148
|
+
const focusChild = useCallback((key) => store.focusChildByKey(id, key, false), [store, id]);
|
|
149
|
+
const focusChildShallow = useCallback((key) => store.focusChildByKey(id, key, true), [store, id]);
|
|
150
|
+
const resolvedBindings = typeof (options == null ? void 0 : options.keybindings) === "function" ? options.keybindings({ next, prev, nextShallow, prevShallow, escape, drillIn, focusChild, focusChildShallow }) : (options == null ? void 0 : options.keybindings) ?? {};
|
|
147
151
|
store.registerKeybindings(id, keybindingRegistrationId, resolvedBindings);
|
|
148
152
|
useEffect4(() => {
|
|
149
153
|
return () => {
|
|
@@ -157,7 +161,19 @@ function useFocusScope(options) {
|
|
|
157
161
|
);
|
|
158
162
|
}
|
|
159
163
|
}, [id, store]);
|
|
160
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
hasFocus,
|
|
167
|
+
isPassive,
|
|
168
|
+
next,
|
|
169
|
+
prev,
|
|
170
|
+
nextShallow,
|
|
171
|
+
prevShallow,
|
|
172
|
+
escape,
|
|
173
|
+
drillIn,
|
|
174
|
+
focusChild,
|
|
175
|
+
focusChildShallow
|
|
176
|
+
};
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
// src/core/focus/FocusScope.tsx
|
|
@@ -212,6 +228,8 @@ var FocusStore = class {
|
|
|
212
228
|
// A keybinding may exist for a node that has not yet appeared in the node tree —
|
|
213
229
|
// this is safe because dispatch only walks nodes in the active branch path.
|
|
214
230
|
keybindings = /* @__PURE__ */ new Map();
|
|
231
|
+
// parentId → focusKey → childId
|
|
232
|
+
keyIndex = /* @__PURE__ */ new Map();
|
|
215
233
|
// ---------------------------------------------------------------------------
|
|
216
234
|
// Subscription
|
|
217
235
|
// ---------------------------------------------------------------------------
|
|
@@ -231,7 +249,7 @@ var FocusStore = class {
|
|
|
231
249
|
// ---------------------------------------------------------------------------
|
|
232
250
|
// Registration
|
|
233
251
|
// ---------------------------------------------------------------------------
|
|
234
|
-
registerNode(id, parentId) {
|
|
252
|
+
registerNode(id, parentId, focusKey) {
|
|
235
253
|
const node = { id, parentId, childrenIds: [] };
|
|
236
254
|
this.nodes.set(id, node);
|
|
237
255
|
this.parentMap.set(id, parentId);
|
|
@@ -251,6 +269,12 @@ var FocusStore = class {
|
|
|
251
269
|
node.childrenIds.push(existingId);
|
|
252
270
|
}
|
|
253
271
|
}
|
|
272
|
+
if (focusKey && parentId) {
|
|
273
|
+
if (!this.keyIndex.has(parentId)) {
|
|
274
|
+
this.keyIndex.set(parentId, /* @__PURE__ */ new Map());
|
|
275
|
+
}
|
|
276
|
+
this.keyIndex.get(parentId).set(focusKey, id);
|
|
277
|
+
}
|
|
254
278
|
if (this.nodes.size === 1) {
|
|
255
279
|
this.focusNode(id);
|
|
256
280
|
}
|
|
@@ -268,6 +292,21 @@ var FocusStore = class {
|
|
|
268
292
|
this.nodes.delete(id);
|
|
269
293
|
this.passiveSet.delete(id);
|
|
270
294
|
this.pendingFocusFirstChild.delete(id);
|
|
295
|
+
if (node.parentId) {
|
|
296
|
+
const parentKeys = this.keyIndex.get(node.parentId);
|
|
297
|
+
if (parentKeys) {
|
|
298
|
+
for (const [key, childId] of parentKeys) {
|
|
299
|
+
if (childId === id) {
|
|
300
|
+
parentKeys.delete(key);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (parentKeys.size === 0) {
|
|
305
|
+
this.keyIndex.delete(node.parentId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this.keyIndex.delete(id);
|
|
271
310
|
if (this.focusedId === id) {
|
|
272
311
|
let candidate = node.parentId;
|
|
273
312
|
while (candidate !== null) {
|
|
@@ -315,6 +354,21 @@ var FocusStore = class {
|
|
|
315
354
|
this.pendingFocusFirstChild.add(parentId);
|
|
316
355
|
}
|
|
317
356
|
}
|
|
357
|
+
focusChildByKey(parentId, key, shallow) {
|
|
358
|
+
var _a;
|
|
359
|
+
const childId = (_a = this.keyIndex.get(parentId)) == null ? void 0 : _a.get(key);
|
|
360
|
+
if (!childId || !this.nodes.has(childId)) return;
|
|
361
|
+
if (shallow) {
|
|
362
|
+
this.focusNode(childId);
|
|
363
|
+
} else {
|
|
364
|
+
const child = this.nodes.get(childId);
|
|
365
|
+
if (child.childrenIds.length > 0) {
|
|
366
|
+
this.focusFirstChild(childId);
|
|
367
|
+
} else {
|
|
368
|
+
this.focusNode(childId);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
318
372
|
// ---------------------------------------------------------------------------
|
|
319
373
|
// Navigation
|
|
320
374
|
// ---------------------------------------------------------------------------
|
package/dist/index.d.ts
CHANGED
|
@@ -47,10 +47,13 @@ type FocusScopeHandle = {
|
|
|
47
47
|
prevShallow: () => void;
|
|
48
48
|
escape: () => void;
|
|
49
49
|
drillIn: () => void;
|
|
50
|
+
focusChild: (key: string) => void;
|
|
51
|
+
focusChildShallow: (key: string) => void;
|
|
50
52
|
};
|
|
51
|
-
type FocusScopeHelpers = Pick<FocusScopeHandle, 'next' | 'prev' | 'nextShallow' | 'prevShallow' | 'escape' | 'drillIn'>;
|
|
53
|
+
type FocusScopeHelpers = Pick<FocusScopeHandle, 'next' | 'prev' | 'nextShallow' | 'prevShallow' | 'escape' | 'drillIn' | 'focusChild' | 'focusChildShallow'>;
|
|
52
54
|
type FocusScopeOptions = {
|
|
53
55
|
parent?: FocusScopeHandle;
|
|
56
|
+
focusKey?: string;
|
|
54
57
|
keybindings?: Keybindings | ((helpers: FocusScopeHelpers) => Keybindings);
|
|
55
58
|
};
|
|
56
59
|
declare function useFocusScope(options?: FocusScopeOptions): FocusScopeHandle;
|
|
@@ -67,6 +70,7 @@ type FocusNodeHandle = {
|
|
|
67
70
|
};
|
|
68
71
|
type FocusNodeOptions = {
|
|
69
72
|
parent?: FocusScopeHandle;
|
|
73
|
+
focusKey?: string;
|
|
70
74
|
};
|
|
71
75
|
declare function useFocusNode(options?: FocusNodeOptions): FocusNodeHandle;
|
|
72
76
|
|
package/dist/index.js
CHANGED
package/dist/ui/index.d.ts
CHANGED
|
@@ -31,8 +31,9 @@ type TextInputProps = {
|
|
|
31
31
|
onSubmit?: (value: string) => void;
|
|
32
32
|
placeholder?: string;
|
|
33
33
|
render?: (props: TextInputRenderProps) => React$1.ReactNode;
|
|
34
|
+
focusKey?: string;
|
|
34
35
|
};
|
|
35
|
-
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
36
|
+
declare function TextInput({ label, value, onChange, onSubmit, placeholder, render, focusKey }: TextInputProps): react_jsx_runtime.JSX.Element;
|
|
36
37
|
|
|
37
38
|
type PaginatorStyle = 'dots' | 'arrows' | 'scrollbar' | 'counter' | 'none';
|
|
38
39
|
type PaginatorProps = {
|
|
@@ -70,8 +71,9 @@ type SelectProps<T> = {
|
|
|
70
71
|
paginatorStyle?: PaginatorStyle;
|
|
71
72
|
wrap?: boolean;
|
|
72
73
|
render?: (props: SelectRenderProps<T>) => React$1.ReactNode;
|
|
74
|
+
focusKey?: string;
|
|
73
75
|
};
|
|
74
|
-
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, gap, maxVisible, paginatorStyle, wrap, render }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
76
|
+
declare function Select<T>({ options, value, onChange, onSubmit, onHighlight, label, immediate, direction, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: SelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
75
77
|
|
|
76
78
|
type MultiSelectRenderProps<T> = {
|
|
77
79
|
option: SelectOption<T>;
|
|
@@ -92,15 +94,17 @@ type MultiSelectProps<T> = {
|
|
|
92
94
|
paginatorStyle?: PaginatorStyle;
|
|
93
95
|
wrap?: boolean;
|
|
94
96
|
render?: (props: MultiSelectRenderProps<T>) => React$1.ReactNode;
|
|
97
|
+
focusKey?: string;
|
|
95
98
|
};
|
|
96
|
-
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, gap, maxVisible, paginatorStyle, wrap, render }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
99
|
+
declare function MultiSelect<T>({ options, value, onChange, onSubmit, onHighlight, label, direction, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: MultiSelectProps<T>): react_jsx_runtime.JSX.Element;
|
|
97
100
|
|
|
98
101
|
type ConfirmProps = {
|
|
99
102
|
message: string;
|
|
100
103
|
defaultValue?: boolean;
|
|
101
104
|
onSubmit: (value: boolean) => void;
|
|
105
|
+
focusKey?: string;
|
|
102
106
|
};
|
|
103
|
-
declare function Confirm({ message, defaultValue, onSubmit }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
107
|
+
declare function Confirm({ message, defaultValue, onSubmit, focusKey }: ConfirmProps): react_jsx_runtime.JSX.Element;
|
|
104
108
|
|
|
105
109
|
type AutocompleteRenderProps<T> = {
|
|
106
110
|
option: SelectOption<T>;
|
|
@@ -122,8 +126,9 @@ type AutocompleteProps<T> = {
|
|
|
122
126
|
paginatorStyle?: PaginatorStyle;
|
|
123
127
|
wrap?: boolean;
|
|
124
128
|
render?: (props: AutocompleteRenderProps<T>) => React$1.ReactNode;
|
|
129
|
+
focusKey?: string;
|
|
125
130
|
};
|
|
126
|
-
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, gap, maxVisible, paginatorStyle, wrap, render }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
131
|
+
declare function Autocomplete<T>({ options, value, onChange, onSubmit, onHighlight, label, placeholder, filter, gap, maxVisible, paginatorStyle, wrap, render, focusKey }: AutocompleteProps<T>): react_jsx_runtime.JSX.Element;
|
|
127
132
|
|
|
128
133
|
type VirtualListRenderProps<T> = {
|
|
129
134
|
item: T;
|
|
@@ -160,6 +165,7 @@ declare const Viewport: React$1.ForwardRefExoticComponent<Omit<BoxProps, "flexDi
|
|
|
160
165
|
height: number;
|
|
161
166
|
keybindings?: boolean;
|
|
162
167
|
footer?: React$1.ReactNode;
|
|
168
|
+
focusKey?: string;
|
|
163
169
|
} & React$1.RefAttributes<ViewportRef>>;
|
|
164
170
|
|
|
165
171
|
type ModalProps = Omit<BoxProps, 'children'> & {
|
package/dist/ui/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
useFocusNode,
|
|
5
5
|
useKeybindingRegistry,
|
|
6
6
|
useKeybindings
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-74PBSWEK.js";
|
|
8
8
|
import {
|
|
9
9
|
CodeBlock
|
|
10
10
|
} from "../chunk-SKSDNDQF.js";
|
|
@@ -136,8 +136,8 @@ function CommandPalette({ onClose, interactive = true, render }) {
|
|
|
136
136
|
import { useReducer, useRef } from "react";
|
|
137
137
|
import { Text as Text2 } from "ink";
|
|
138
138
|
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
139
|
-
function TextInput({ label, value, onChange, onSubmit, placeholder, render }) {
|
|
140
|
-
const focus = useFocusNode();
|
|
139
|
+
function TextInput({ label, value, onChange, onSubmit, placeholder, render, focusKey }) {
|
|
140
|
+
const focus = useFocusNode({ focusKey });
|
|
141
141
|
const cursorRef = useRef(value.length);
|
|
142
142
|
const [, forceRender] = useReducer((c) => c + 1, 0);
|
|
143
143
|
const cursor = Math.min(cursorRef.current, value.length);
|
|
@@ -351,7 +351,8 @@ function Select({
|
|
|
351
351
|
maxVisible,
|
|
352
352
|
paginatorStyle,
|
|
353
353
|
wrap = true,
|
|
354
|
-
render
|
|
354
|
+
render,
|
|
355
|
+
focusKey
|
|
355
356
|
}) {
|
|
356
357
|
const seen = /* @__PURE__ */ new Set();
|
|
357
358
|
for (const opt of options) {
|
|
@@ -361,7 +362,7 @@ function Select({
|
|
|
361
362
|
}
|
|
362
363
|
seen.add(key);
|
|
363
364
|
}
|
|
364
|
-
const focus = useFocusNode();
|
|
365
|
+
const focus = useFocusNode({ focusKey });
|
|
365
366
|
const theme = useTheme();
|
|
366
367
|
const [highlightIndex, setHighlightIndex] = useState3(0);
|
|
367
368
|
const safeIndex = options.length === 0 ? -1 : Math.min(highlightIndex, options.length - 1);
|
|
@@ -438,7 +439,8 @@ function MultiSelect({
|
|
|
438
439
|
maxVisible,
|
|
439
440
|
paginatorStyle,
|
|
440
441
|
wrap = true,
|
|
441
|
-
render
|
|
442
|
+
render,
|
|
443
|
+
focusKey
|
|
442
444
|
}) {
|
|
443
445
|
const seen = /* @__PURE__ */ new Set();
|
|
444
446
|
for (const opt of options) {
|
|
@@ -448,7 +450,7 @@ function MultiSelect({
|
|
|
448
450
|
}
|
|
449
451
|
seen.add(key);
|
|
450
452
|
}
|
|
451
|
-
const focus = useFocusNode();
|
|
453
|
+
const focus = useFocusNode({ focusKey });
|
|
452
454
|
const theme = useTheme();
|
|
453
455
|
const [highlightIndex, setHighlightIndex] = useState4(0);
|
|
454
456
|
const [internalSelected, setInternalSelected] = useState4([]);
|
|
@@ -515,8 +517,8 @@ function MultiSelect({
|
|
|
515
517
|
// src/ui/Confirm.tsx
|
|
516
518
|
import { Text as Text6 } from "ink";
|
|
517
519
|
import { jsxs as jsxs7 } from "react/jsx-runtime";
|
|
518
|
-
function Confirm({ message, defaultValue = true, onSubmit }) {
|
|
519
|
-
const focus = useFocusNode();
|
|
520
|
+
function Confirm({ message, defaultValue = true, onSubmit, focusKey }) {
|
|
521
|
+
const focus = useFocusNode({ focusKey });
|
|
520
522
|
useKeybindings(focus, {
|
|
521
523
|
y: () => onSubmit(true),
|
|
522
524
|
n: () => onSubmit(false),
|
|
@@ -558,7 +560,8 @@ function Autocomplete({
|
|
|
558
560
|
maxVisible,
|
|
559
561
|
paginatorStyle,
|
|
560
562
|
wrap = true,
|
|
561
|
-
render
|
|
563
|
+
render,
|
|
564
|
+
focusKey
|
|
562
565
|
}) {
|
|
563
566
|
const seen = /* @__PURE__ */ new Set();
|
|
564
567
|
for (const opt of options) {
|
|
@@ -568,7 +571,7 @@ function Autocomplete({
|
|
|
568
571
|
}
|
|
569
572
|
seen.add(key);
|
|
570
573
|
}
|
|
571
|
-
const focus = useFocusNode();
|
|
574
|
+
const focus = useFocusNode({ focusKey });
|
|
572
575
|
const theme = useTheme();
|
|
573
576
|
const [query, setQuery] = useState5("");
|
|
574
577
|
const [highlightIndex, setHighlightIndex] = useState5(0);
|
|
@@ -718,8 +721,8 @@ function MeasurableItem({
|
|
|
718
721
|
}, [index, onMeasure, children]);
|
|
719
722
|
return /* @__PURE__ */ jsx8(Box7, { ref, flexShrink: 0, width: "100%", flexDirection: "column", children });
|
|
720
723
|
}
|
|
721
|
-
var Viewport = forwardRef(function Viewport2({ children, height, keybindings: enableKeybindings = true, footer, ...boxProps }, ref) {
|
|
722
|
-
const focus = useFocusNode();
|
|
724
|
+
var Viewport = forwardRef(function Viewport2({ children, height, keybindings: enableKeybindings = true, footer, focusKey, ...boxProps }, ref) {
|
|
725
|
+
const focus = useFocusNode({ focusKey });
|
|
723
726
|
const [scrollOffset, setScrollOffset] = useState6(0);
|
|
724
727
|
const contentHeightRef = useRef4(0);
|
|
725
728
|
const itemHeightsRef = useRef4({});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "giggles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"play": "f() { tsx --watch playground/examples/$1.tsx; }; f",
|
|
44
44
|
"record": "f() { vhs playground/tapes/$1.tape; }; f",
|
|
45
45
|
"dev:docs": "pnpm build && concurrently --kill-others \"pnpm build:watch\" \"pnpm --filter documentation dev\"",
|
|
46
|
-
"lint": "prettier --write . && eslint . --fix"
|
|
46
|
+
"lint": "prettier --write --loglevel warn . && eslint . --fix --quiet"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"dist"
|