@tooee/choose 0.7.3 → 0.8.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/choose-filter.d.ts.map +1 -1
- package/dist/choose-filter.js +11 -2
- package/dist/choose-filter.js.map +1 -1
- package/dist/choose-list.d.ts.map +1 -1
- package/dist/choose-list.js +10 -1
- package/dist/choose-list.js.map +1 -1
- package/dist/choose-panel.d.ts +7 -9
- package/dist/choose-panel.d.ts.map +1 -1
- package/dist/choose-panel.js +5 -8
- package/dist/choose-panel.js.map +1 -1
- package/dist/choose-store.d.ts +57 -0
- package/dist/choose-store.d.ts.map +1 -0
- package/dist/choose-store.js +102 -0
- package/dist/choose-store.js.map +1 -0
- package/dist/choose.d.ts +5 -5
- package/dist/choose.d.ts.map +1 -1
- package/dist/choose.js +26 -12
- package/dist/choose.js.map +1 -1
- package/dist/fuzzy.d.ts.map +1 -1
- package/dist/fuzzy.js +2 -14
- package/dist/fuzzy.js.map +1 -1
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +17 -5
- package/dist/source.js.map +1 -1
- package/dist/use-choose-dialog.d.ts.map +1 -1
- package/dist/use-choose-dialog.js +22 -84
- package/dist/use-choose-dialog.js.map +1 -1
- package/dist/use-choose.d.ts +1 -1
- package/dist/use-choose.d.ts.map +1 -1
- package/dist/use-choose.js +236 -264
- package/dist/use-choose.js.map +1 -1
- package/package.json +10 -7
- package/src/choose-filter.tsx +10 -2
- package/src/choose-list.tsx +9 -1
- package/src/choose-panel.tsx +18 -71
- package/src/choose-store.ts +146 -0
- package/src/choose.tsx +43 -27
- package/src/fuzzy.ts +2 -17
- package/src/launch.tsx +7 -2
- package/src/source.ts +24 -6
- package/src/use-choose-dialog.tsx +30 -98
- package/src/use-choose.ts +273 -278
package/src/use-choose.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import type { InputRenderable, KeyEvent } from "@opentui/core";
|
|
3
|
+
import { useSelector } from "@xstate/store-react";
|
|
3
4
|
import {
|
|
4
5
|
useActiveCommandSurface,
|
|
5
6
|
useActions,
|
|
6
|
-
useCommand,
|
|
7
7
|
useCommandSurfaceId,
|
|
8
|
+
useLatest,
|
|
9
|
+
useLazyRef,
|
|
8
10
|
useMode,
|
|
9
11
|
useProvideCommandContext,
|
|
10
12
|
useSetMode,
|
|
11
13
|
} from "@tooee/commands";
|
|
12
|
-
import type { ActionDefinition, Mode } from "@tooee/commands";
|
|
13
|
-
import {
|
|
14
|
+
import type { ActionDefinition, CommandContext, Mode } from "@tooee/commands";
|
|
15
|
+
import {
|
|
16
|
+
createChooseStore,
|
|
17
|
+
selectActiveIndex,
|
|
18
|
+
selectError,
|
|
19
|
+
selectFilterQuery,
|
|
20
|
+
selectItems,
|
|
21
|
+
selectLoading,
|
|
22
|
+
selectMatches,
|
|
23
|
+
selectReloadRevision,
|
|
24
|
+
selectSelectedOriginalIndices,
|
|
25
|
+
} from "./choose-store.js";
|
|
14
26
|
import type { FuzzyMatch } from "./fuzzy.js";
|
|
15
27
|
import { chooseSourceError, loadChooseSource } from "./source.js";
|
|
16
28
|
import type { ChooseItem, ChooseResult, ChooseSource } from "./types.js";
|
|
@@ -18,7 +30,7 @@ import type { ChooseItem, ChooseResult, ChooseSource } from "./types.js";
|
|
|
18
30
|
declare module "@tooee/commands" {
|
|
19
31
|
interface CommandContext {
|
|
20
32
|
/** Contributed by chooser primitives: current filtering and selection state. */
|
|
21
|
-
choose
|
|
33
|
+
choose?: {
|
|
22
34
|
activeItem: ChooseItem | undefined;
|
|
23
35
|
selectedItems: ChooseItem[];
|
|
24
36
|
filterQuery: string;
|
|
@@ -90,6 +102,16 @@ export interface UseChooseResult {
|
|
|
90
102
|
view: ChooseViewModel;
|
|
91
103
|
}
|
|
92
104
|
|
|
105
|
+
interface ChooseCommandContextContribution {
|
|
106
|
+
choose: CommandContext["choose"];
|
|
107
|
+
exit?: () => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface ChooseKeymapDefinition extends Omit<ActionDefinition, "group" | "when"> {
|
|
111
|
+
group: () => ChooseCommandGroup;
|
|
112
|
+
when?: () => boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
/**
|
|
94
116
|
* Shared headless chooser: source lifecycle, fuzzy matches, selection,
|
|
95
117
|
* command context, controller, and all built-in keyboard commands.
|
|
@@ -97,20 +119,29 @@ export interface UseChooseResult {
|
|
|
97
119
|
export const useChoose = function useChoose(options: UseChooseOptions): UseChooseResult {
|
|
98
120
|
const { source, multi = false, initialFilter = "", commandScope = "choose" } = options;
|
|
99
121
|
const initialItems = Array.isArray(source) ? source : [];
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const [reloadRevision, setReloadRevision] = useState(0);
|
|
122
|
+
const store = useLazyRef(() =>
|
|
123
|
+
createChooseStore({
|
|
124
|
+
initialFilter,
|
|
125
|
+
items: initialItems,
|
|
126
|
+
loading: !Array.isArray(source),
|
|
127
|
+
}),
|
|
128
|
+
).current;
|
|
108
129
|
const filterRef = useRef<InputRenderable>(null);
|
|
109
|
-
const
|
|
110
|
-
|
|
130
|
+
const initialArraySourceRef = useLazyRef<ChooseItem[] | null>(() =>
|
|
131
|
+
Array.isArray(source) ? source : null,
|
|
132
|
+
);
|
|
111
133
|
const didHandleInitialSourceRef = useRef(false);
|
|
112
134
|
|
|
113
|
-
const
|
|
135
|
+
const items = useSelector(store, (snapshot) => selectItems(snapshot.context));
|
|
136
|
+
const matches = useSelector(store, (snapshot) => selectMatches(snapshot.context));
|
|
137
|
+
const filterQuery = useSelector(store, (snapshot) => selectFilterQuery(snapshot.context));
|
|
138
|
+
const activeIndex = useSelector(store, (snapshot) => selectActiveIndex(snapshot.context));
|
|
139
|
+
const selectedOriginalIndices = useSelector(store, (snapshot) =>
|
|
140
|
+
selectSelectedOriginalIndices(snapshot.context),
|
|
141
|
+
);
|
|
142
|
+
const loading = useSelector(store, (snapshot) => selectLoading(snapshot.context));
|
|
143
|
+
const error = useSelector(store, (snapshot) => selectError(snapshot.context));
|
|
144
|
+
const reloadRevision = useSelector(store, (snapshot) => selectReloadRevision(snapshot.context));
|
|
114
145
|
const activeItem = matches[activeIndex]?.item;
|
|
115
146
|
const selectedItems = useMemo(() => {
|
|
116
147
|
if (!multi) {
|
|
@@ -128,170 +159,108 @@ export const useChoose = function useChoose(options: UseChooseOptions): UseChoos
|
|
|
128
159
|
|
|
129
160
|
const mode = useMode();
|
|
130
161
|
const setMode = useSetMode();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const optionsRef = useRef(options);
|
|
135
|
-
optionsRef.current = options;
|
|
136
|
-
const itemsRef = useRef(items);
|
|
137
|
-
itemsRef.current = items;
|
|
138
|
-
const matchesRef = useRef(matches);
|
|
139
|
-
matchesRef.current = matches;
|
|
140
|
-
const filterQueryRef = useRef(filterQuery);
|
|
141
|
-
filterQueryRef.current = filterQuery;
|
|
142
|
-
const activeIndexRef = useRef(activeIndex);
|
|
143
|
-
activeIndexRef.current = activeIndex;
|
|
144
|
-
const selectedRef = useRef(selectedOriginalIndices);
|
|
145
|
-
selectedRef.current = selectedOriginalIndices;
|
|
146
|
-
const multiRef = useRef(multi);
|
|
147
|
-
multiRef.current = multi;
|
|
148
|
-
const modeRef = useRef(mode);
|
|
149
|
-
modeRef.current = mode;
|
|
150
|
-
|
|
151
|
-
const replaceItems = useCallback((nextItems: ChooseItem[]) => {
|
|
152
|
-
itemsRef.current = nextItems;
|
|
153
|
-
matchesRef.current = fuzzyFilter(nextItems, filterQueryRef.current);
|
|
154
|
-
activeIndexRef.current = 0;
|
|
155
|
-
selectedRef.current = new Set();
|
|
156
|
-
setItems(nextItems);
|
|
157
|
-
setActiveIndex(0);
|
|
158
|
-
setSelectedOriginalIndices(selectedRef.current);
|
|
159
|
-
}, []);
|
|
162
|
+
const optionsRef = useLatest(options);
|
|
163
|
+
const multiRef = useLatest(multi);
|
|
164
|
+
const modeRef = useLatest(mode);
|
|
160
165
|
|
|
161
166
|
useEffect(() => {
|
|
162
|
-
requestIdRef.current += 1;
|
|
163
|
-
const requestId = requestIdRef.current;
|
|
164
167
|
let active = true;
|
|
165
168
|
const deactivate = () => {
|
|
166
169
|
active = false;
|
|
167
170
|
};
|
|
168
|
-
let result: ChooseItem[] | Promise<ChooseItem[]>;
|
|
169
171
|
|
|
170
|
-
// Direct arrays seed
|
|
171
|
-
//
|
|
172
|
+
// Direct arrays seed the store before the first render. Avoid replacing
|
|
173
|
+
// state after mount, which could overwrite immediate controller calls.
|
|
172
174
|
if (
|
|
173
175
|
!didHandleInitialSourceRef.current &&
|
|
174
176
|
Array.isArray(source) &&
|
|
175
177
|
source === initialArraySourceRef.current
|
|
176
178
|
) {
|
|
177
179
|
didHandleInitialSourceRef.current = true;
|
|
178
|
-
setLoading(false);
|
|
179
180
|
return deactivate;
|
|
180
181
|
}
|
|
181
182
|
didHandleInitialSourceRef.current = true;
|
|
182
183
|
|
|
183
|
-
|
|
184
|
+
store.trigger.requestStarted({});
|
|
185
|
+
const { requestId } = store.getSnapshot().context;
|
|
186
|
+
let result: ChooseItem[] | Promise<ChooseItem[]>;
|
|
184
187
|
try {
|
|
185
188
|
result = loadChooseSource(source);
|
|
186
189
|
} catch (loadError) {
|
|
187
|
-
|
|
188
|
-
return deactivate;
|
|
189
|
-
}
|
|
190
|
-
replaceItems([]);
|
|
191
|
-
setError(chooseSourceError(loadError));
|
|
192
|
-
setLoading(false);
|
|
190
|
+
store.trigger.loadFailed({ error: chooseSourceError(loadError), requestId });
|
|
193
191
|
return deactivate;
|
|
194
192
|
}
|
|
195
193
|
|
|
196
194
|
if (result instanceof Promise) {
|
|
197
|
-
|
|
195
|
+
store.trigger.loadingStarted({ requestId });
|
|
198
196
|
void (async () => {
|
|
199
197
|
try {
|
|
200
198
|
const loaded = await result;
|
|
201
|
-
if (
|
|
202
|
-
|
|
199
|
+
if (active) {
|
|
200
|
+
store.trigger.loadSucceeded({ items: loaded, requestId });
|
|
203
201
|
}
|
|
204
|
-
replaceItems(loaded);
|
|
205
|
-
setError(null);
|
|
206
|
-
setLoading(false);
|
|
207
202
|
} catch (loadError) {
|
|
208
|
-
if (
|
|
209
|
-
|
|
203
|
+
if (active) {
|
|
204
|
+
store.trigger.loadFailed({ error: chooseSourceError(loadError), requestId });
|
|
210
205
|
}
|
|
211
|
-
replaceItems([]);
|
|
212
|
-
setError(chooseSourceError(loadError));
|
|
213
|
-
setLoading(false);
|
|
214
206
|
}
|
|
215
207
|
})();
|
|
216
208
|
return deactivate;
|
|
217
209
|
}
|
|
218
210
|
|
|
219
|
-
|
|
220
|
-
setLoading(false);
|
|
211
|
+
store.trigger.loadSucceeded({ items: result, requestId });
|
|
221
212
|
return deactivate;
|
|
222
|
-
}, [source, reloadRevision,
|
|
223
|
-
|
|
224
|
-
const setFilter = useCallback((query: string) => {
|
|
225
|
-
// Controlled OpenTUI inputs may echo a programmatic `value` update through
|
|
226
|
-
// onInput. Only a real query change resets navigation.
|
|
227
|
-
if (query === filterQueryRef.current) {
|
|
228
|
-
setFilterQuery(query);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
filterQueryRef.current = query;
|
|
232
|
-
matchesRef.current = fuzzyFilter(itemsRef.current, query);
|
|
233
|
-
activeIndexRef.current = 0;
|
|
234
|
-
setFilterQuery(query);
|
|
235
|
-
setActiveIndex(0);
|
|
236
|
-
}, []);
|
|
237
|
-
|
|
238
|
-
const updateActiveIndex = useCallback((index: number) => {
|
|
239
|
-
const lastIndex = Math.max(0, matchesRef.current.length - 1);
|
|
240
|
-
const next = Math.min(lastIndex, Math.max(0, index));
|
|
241
|
-
activeIndexRef.current = next;
|
|
242
|
-
setActiveIndex(next);
|
|
243
|
-
}, []);
|
|
213
|
+
}, [source, reloadRevision, store, initialArraySourceRef]);
|
|
244
214
|
|
|
215
|
+
const setFilter = useCallback(
|
|
216
|
+
(query: string) => {
|
|
217
|
+
store.trigger.filterChanged({ query });
|
|
218
|
+
},
|
|
219
|
+
[store],
|
|
220
|
+
);
|
|
221
|
+
const updateActiveIndex = useCallback(
|
|
222
|
+
(index: number) => {
|
|
223
|
+
store.trigger.activeIndexSet({ index });
|
|
224
|
+
},
|
|
225
|
+
[store],
|
|
226
|
+
);
|
|
245
227
|
const moveUp = useCallback(() => {
|
|
246
|
-
|
|
247
|
-
}, [
|
|
228
|
+
store.trigger.moved({ delta: -1 });
|
|
229
|
+
}, [store]);
|
|
248
230
|
const moveDown = useCallback(() => {
|
|
249
|
-
|
|
250
|
-
}, [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
231
|
+
store.trigger.moved({ delta: 1 });
|
|
232
|
+
}, [store]);
|
|
233
|
+
const getActiveItem = useCallback(() => {
|
|
234
|
+
const { context } = store.getSnapshot();
|
|
235
|
+
return context.matches[context.activeIndex]?.item;
|
|
236
|
+
}, [store]);
|
|
254
237
|
const getSelectedItems = useCallback((): ChooseItem[] => {
|
|
255
|
-
const
|
|
238
|
+
const { context } = store.getSnapshot();
|
|
239
|
+
const active = context.matches[context.activeIndex]?.item;
|
|
256
240
|
if (!multiRef.current) {
|
|
257
241
|
return active === undefined ? [] : [active];
|
|
258
242
|
}
|
|
259
|
-
const selected = [...
|
|
260
|
-
const item =
|
|
243
|
+
const selected = [...context.selectedOriginalIndices].flatMap((index) => {
|
|
244
|
+
const item = context.items[index];
|
|
261
245
|
return item === undefined ? [] : [item];
|
|
262
246
|
});
|
|
263
247
|
if (selected.length > 0) {
|
|
264
248
|
return selected;
|
|
265
249
|
}
|
|
266
250
|
return active === undefined ? [] : [active];
|
|
267
|
-
}, [
|
|
268
|
-
|
|
251
|
+
}, [multiRef, store]);
|
|
269
252
|
const toggleActive = useCallback(() => {
|
|
270
|
-
if (
|
|
271
|
-
|
|
253
|
+
if (multiRef.current) {
|
|
254
|
+
store.trigger.activeToggled({});
|
|
272
255
|
}
|
|
273
|
-
|
|
274
|
-
if (originalIndex === undefined) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const next = new Set(selectedRef.current);
|
|
278
|
-
if (next.has(originalIndex)) {
|
|
279
|
-
next.delete(originalIndex);
|
|
280
|
-
} else {
|
|
281
|
-
next.add(originalIndex);
|
|
282
|
-
}
|
|
283
|
-
selectedRef.current = next;
|
|
284
|
-
setSelectedOriginalIndices(next);
|
|
285
|
-
}, []);
|
|
286
|
-
|
|
256
|
+
}, [multiRef, store]);
|
|
287
257
|
const submit = useCallback(() => {
|
|
288
258
|
void optionsRef.current.onSubmit({ items: getSelectedItems() });
|
|
289
|
-
}, [getSelectedItems]);
|
|
290
|
-
|
|
291
|
-
const cancel = useCallback(() => optionsRef.current.onCancel?.(), []);
|
|
259
|
+
}, [getSelectedItems, optionsRef]);
|
|
260
|
+
const cancel = useCallback(() => optionsRef.current.onCancel?.(), [optionsRef]);
|
|
292
261
|
const reload = useCallback(() => {
|
|
293
|
-
|
|
294
|
-
}, []);
|
|
262
|
+
store.trigger.reloadRequested({});
|
|
263
|
+
}, [store]);
|
|
295
264
|
const setModeExternal = useCallback(
|
|
296
265
|
(nextMode: Mode) => {
|
|
297
266
|
setMode(nextMode);
|
|
@@ -299,158 +268,185 @@ export const useChoose = function useChoose(options: UseChooseOptions): UseChoos
|
|
|
299
268
|
[setMode],
|
|
300
269
|
);
|
|
301
270
|
|
|
302
|
-
useProvideCommandContext(() =>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
271
|
+
useProvideCommandContext(() => {
|
|
272
|
+
const context: ChooseCommandContextContribution = {
|
|
273
|
+
choose: {
|
|
274
|
+
activeItem: getActiveItem(),
|
|
275
|
+
filterQuery: store.getSnapshot().context.filterQuery,
|
|
276
|
+
selectedItems: getSelectedItems(),
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
if (optionsRef.current.onCancel !== undefined) {
|
|
280
|
+
context.exit = cancel;
|
|
281
|
+
}
|
|
282
|
+
return context;
|
|
283
|
+
});
|
|
310
284
|
|
|
311
285
|
useActions(options.commands);
|
|
312
286
|
|
|
313
287
|
const enabled = useCallback(
|
|
314
288
|
(group: ChooseCommandGroup) => !(optionsRef.current.disable?.includes(group) ?? false),
|
|
315
|
-
[],
|
|
289
|
+
[optionsRef],
|
|
316
290
|
);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
291
|
+
const builtInActions = useMemo<ActionDefinition[]>(() => {
|
|
292
|
+
const definitions: ChooseKeymapDefinition[] = [
|
|
293
|
+
{
|
|
294
|
+
group: () => (modeRef.current === "insert" ? "mode" : "cancel"),
|
|
295
|
+
handler: () => {
|
|
296
|
+
if (modeRef.current === "insert") {
|
|
297
|
+
setMode("cursor");
|
|
298
|
+
} else {
|
|
299
|
+
cancel();
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
hidden: true,
|
|
303
|
+
hotkey: "Escape",
|
|
304
|
+
id: `${commandScope}:escape`,
|
|
305
|
+
modes: ["insert", "cursor"],
|
|
306
|
+
title: "Back / cancel",
|
|
307
|
+
when: () =>
|
|
308
|
+
modeRef.current === "insert" ? true : optionsRef.current.onCancel !== undefined,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
group: () => "cancel",
|
|
312
|
+
handler: cancel,
|
|
313
|
+
hidden: true,
|
|
314
|
+
hotkey: "q",
|
|
315
|
+
id: `${commandScope}:cancel`,
|
|
316
|
+
modes: ["cursor"],
|
|
317
|
+
title: "Cancel",
|
|
318
|
+
when: () => optionsRef.current.onCancel !== undefined,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
group: () => "mode",
|
|
322
|
+
handler: () => {
|
|
323
|
+
setMode("insert");
|
|
324
|
+
},
|
|
325
|
+
hidden: true,
|
|
326
|
+
hotkey: "i",
|
|
327
|
+
id: `${commandScope}:insert-mode-i`,
|
|
328
|
+
modes: ["cursor"],
|
|
329
|
+
title: "Insert mode",
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
group: () => "mode",
|
|
333
|
+
handler: () => {
|
|
334
|
+
setMode("insert");
|
|
335
|
+
},
|
|
336
|
+
hidden: true,
|
|
337
|
+
hotkey: "a",
|
|
338
|
+
id: `${commandScope}:insert-mode-a`,
|
|
339
|
+
modes: ["cursor"],
|
|
340
|
+
title: "Insert mode",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
group: () => "navigation",
|
|
344
|
+
handler: moveDown,
|
|
345
|
+
hidden: true,
|
|
346
|
+
hotkey: "j",
|
|
347
|
+
id: `${commandScope}:move-down-vim`,
|
|
348
|
+
modes: ["cursor"],
|
|
349
|
+
title: "Move down",
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
group: () => "navigation",
|
|
353
|
+
handler: moveUp,
|
|
354
|
+
hidden: true,
|
|
355
|
+
hotkey: "k",
|
|
356
|
+
id: `${commandScope}:move-up-vim`,
|
|
357
|
+
modes: ["cursor"],
|
|
358
|
+
title: "Move up",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
group: () => "submit",
|
|
362
|
+
handler: submit,
|
|
363
|
+
hidden: true,
|
|
364
|
+
hotkey: "Enter",
|
|
365
|
+
id: `${commandScope}:confirm`,
|
|
366
|
+
modes: ["insert", "cursor"],
|
|
367
|
+
title: "Confirm",
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
group: () => "navigation",
|
|
371
|
+
handler: moveUp,
|
|
372
|
+
hidden: true,
|
|
373
|
+
hotkey: "up",
|
|
374
|
+
id: `${commandScope}:move-up`,
|
|
375
|
+
modes: ["insert", "cursor"],
|
|
376
|
+
title: "Move up",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
group: () => "navigation",
|
|
380
|
+
handler: moveUp,
|
|
381
|
+
hidden: true,
|
|
382
|
+
hotkey: "ctrl+p",
|
|
383
|
+
id: `${commandScope}:move-up-ctrl-p`,
|
|
384
|
+
modes: ["insert", "cursor"],
|
|
385
|
+
title: "Move up",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
group: () => "navigation",
|
|
389
|
+
handler: moveDown,
|
|
390
|
+
hidden: true,
|
|
391
|
+
hotkey: "down",
|
|
392
|
+
id: `${commandScope}:move-down`,
|
|
393
|
+
modes: ["insert", "cursor"],
|
|
394
|
+
title: "Move down",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
group: () => "navigation",
|
|
398
|
+
handler: moveDown,
|
|
399
|
+
hidden: true,
|
|
400
|
+
hotkey: "ctrl+n",
|
|
401
|
+
id: `${commandScope}:move-down-ctrl-n`,
|
|
402
|
+
modes: ["insert", "cursor"],
|
|
403
|
+
title: "Move down",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
group: () => "multi-select",
|
|
407
|
+
handler: () => {
|
|
408
|
+
toggleActive();
|
|
409
|
+
moveDown();
|
|
410
|
+
},
|
|
411
|
+
hidden: true,
|
|
412
|
+
hotkey: "Tab",
|
|
413
|
+
id: `${commandScope}:toggle-next`,
|
|
414
|
+
modes: ["insert", "cursor"],
|
|
415
|
+
title: "Toggle selection and move down",
|
|
416
|
+
when: () => multiRef.current,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
group: () => "multi-select",
|
|
420
|
+
handler: () => {
|
|
421
|
+
toggleActive();
|
|
422
|
+
moveUp();
|
|
423
|
+
},
|
|
424
|
+
hidden: true,
|
|
425
|
+
hotkey: "shift+Tab",
|
|
426
|
+
id: `${commandScope}:toggle-previous`,
|
|
427
|
+
modes: ["insert", "cursor"],
|
|
428
|
+
title: "Toggle selection and move up",
|
|
429
|
+
when: () => multiRef.current,
|
|
430
|
+
},
|
|
431
|
+
];
|
|
432
|
+
return definitions.map(({ group, when, ...definition }) => ({
|
|
433
|
+
...definition,
|
|
434
|
+
when: () => enabled(group()) && (when?.() ?? true),
|
|
435
|
+
}));
|
|
436
|
+
}, [
|
|
437
|
+
cancel,
|
|
438
|
+
commandScope,
|
|
439
|
+
enabled,
|
|
440
|
+
modeRef,
|
|
441
|
+
moveDown,
|
|
442
|
+
moveUp,
|
|
443
|
+
multiRef,
|
|
444
|
+
optionsRef,
|
|
445
|
+
setMode,
|
|
446
|
+
submit,
|
|
447
|
+
toggleActive,
|
|
448
|
+
]);
|
|
449
|
+
useActions(builtInActions);
|
|
454
450
|
|
|
455
451
|
const surfaceId = useCommandSurfaceId();
|
|
456
452
|
const activeSurface = useActiveCommandSurface();
|
|
@@ -475,17 +471,16 @@ export const useChoose = function useChoose(options: UseChooseOptions): UseChoos
|
|
|
475
471
|
moveDown();
|
|
476
472
|
}
|
|
477
473
|
},
|
|
478
|
-
[enabled, moveDown, moveUp, toggleActive],
|
|
474
|
+
[enabled, modeRef, moveDown, moveUp, multiRef, toggleActive],
|
|
479
475
|
);
|
|
480
476
|
|
|
481
|
-
const
|
|
482
|
-
controllerRef.current ??= {
|
|
477
|
+
const controller = useLazyRef<ChooseController>(() => ({
|
|
483
478
|
cancel,
|
|
484
479
|
clearFilter: () => {
|
|
485
480
|
setFilter("");
|
|
486
481
|
},
|
|
487
482
|
getActiveItem,
|
|
488
|
-
getFilter: () =>
|
|
483
|
+
getFilter: () => store.getSnapshot().context.filterQuery,
|
|
489
484
|
getSelectedItems,
|
|
490
485
|
get mode() {
|
|
491
486
|
return modeRef.current;
|
|
@@ -498,10 +493,10 @@ export const useChoose = function useChoose(options: UseChooseOptions): UseChoos
|
|
|
498
493
|
setMode: setModeExternal,
|
|
499
494
|
submit,
|
|
500
495
|
toggleActive,
|
|
501
|
-
};
|
|
496
|
+
})).current;
|
|
502
497
|
|
|
503
498
|
return {
|
|
504
|
-
controller
|
|
499
|
+
controller,
|
|
505
500
|
state: {
|
|
506
501
|
activeIndex,
|
|
507
502
|
activeItem,
|