@tooee/search 0.2.1 → 0.2.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.
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/nav-search-store.d.ts +82 -0
- package/dist/nav-search-store.d.ts.map +1 -0
- package/dist/nav-search-store.js +193 -0
- package/dist/nav-search-store.js.map +1 -0
- package/dist/search-hook.d.ts +10 -11
- package/dist/search-hook.d.ts.map +1 -1
- package/dist/search-hook.js +58 -93
- package/dist/search-hook.js.map +1 -1
- package/package.json +5 -3
- package/src/index.ts +21 -0
- package/src/nav-search-store.ts +278 -0
- package/src/search-hook.ts +96 -123
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { useSearch } from "./search-hook.js";
|
|
2
|
+
export { useNavSearchStore, useSearchBindings } from "./search-hook.js";
|
|
3
|
+
export { createNavSearchStore } from "./nav-search-store.js";
|
|
4
|
+
export type { NavSearchStore, NavSearchContext, NavSearchEvents, RowKey, } from "./nav-search-store.js";
|
|
5
|
+
export { selectCursor, selectRowKeys, selectSelectionAnchor, selectToggledKeys, selectSearchStatus, selectSearchActive, selectSearchQuery, selectMatches, selectCurrentMatchIndex, deriveSelection, resolveIndex, } from "./nav-search-store.js";
|
|
2
6
|
export type { UseSearchOptions, SearchState } from "./search-hook.js";
|
|
3
7
|
export { findMatchingLines } from "./search.js";
|
|
4
8
|
export { SearchBar } from "./SearchBar.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAC5D,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,MAAM,GACP,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,uBAAuB,EACvB,eAAe,EACf,YAAY,GACb,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { useSearch } from "./search-hook.js";
|
|
2
|
+
export { useNavSearchStore, useSearchBindings } from "./search-hook.js";
|
|
3
|
+
export { createNavSearchStore } from "./nav-search-store.js";
|
|
4
|
+
export { selectCursor, selectRowKeys, selectSelectionAnchor, selectToggledKeys, selectSearchStatus, selectSearchActive, selectSearchQuery, selectMatches, selectCurrentMatchIndex, deriveSelection, resolveIndex, } from "./nav-search-store.js";
|
|
2
5
|
export { findMatchingLines } from "./search.js";
|
|
3
6
|
export { SearchBar } from "./SearchBar.js";
|
|
4
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAO5D,OAAO,EACL,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,uBAAuB,EACvB,eAAe,EACf,YAAY,GACb,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Mode } from "@tooee/commands";
|
|
2
|
+
export type RowKey = string | number | bigint;
|
|
3
|
+
export type SearchStatus = "idle" | "editing" | "committed";
|
|
4
|
+
export interface NavSearchContext {
|
|
5
|
+
rowKeys: readonly RowKey[];
|
|
6
|
+
cursor: number | null;
|
|
7
|
+
selectionAnchor: number | null;
|
|
8
|
+
toggledKeys: ReadonlySet<RowKey>;
|
|
9
|
+
search: {
|
|
10
|
+
status: SearchStatus;
|
|
11
|
+
query: string;
|
|
12
|
+
committedQuery: string;
|
|
13
|
+
matches: readonly number[];
|
|
14
|
+
currentMatchIndex: number;
|
|
15
|
+
preSearchMode: Mode;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface NavSearchDeps {
|
|
19
|
+
isSelectable(index: number): boolean;
|
|
20
|
+
}
|
|
21
|
+
export type NavSearchEvents = {
|
|
22
|
+
rowsChanged: {
|
|
23
|
+
keys: readonly RowKey[];
|
|
24
|
+
preserveCursorByKey?: boolean;
|
|
25
|
+
};
|
|
26
|
+
move: {
|
|
27
|
+
delta: number;
|
|
28
|
+
};
|
|
29
|
+
jump: {
|
|
30
|
+
index: number;
|
|
31
|
+
direction: 1 | -1;
|
|
32
|
+
};
|
|
33
|
+
setCursor: {
|
|
34
|
+
index: number;
|
|
35
|
+
};
|
|
36
|
+
enterSelect: {};
|
|
37
|
+
cancelSelect: {};
|
|
38
|
+
toggleCurrent: {};
|
|
39
|
+
toggleAndMove: {
|
|
40
|
+
delta: number;
|
|
41
|
+
};
|
|
42
|
+
searchStarted: {
|
|
43
|
+
mode: Mode;
|
|
44
|
+
};
|
|
45
|
+
searchChanged: {
|
|
46
|
+
query: string;
|
|
47
|
+
matches: readonly number[];
|
|
48
|
+
};
|
|
49
|
+
searchSubmitted: {};
|
|
50
|
+
searchCancelled: {};
|
|
51
|
+
searchNext: {};
|
|
52
|
+
searchPrevious: {};
|
|
53
|
+
};
|
|
54
|
+
type NavSearchEmitted = {
|
|
55
|
+
restoreMode: {
|
|
56
|
+
mode: Mode;
|
|
57
|
+
};
|
|
58
|
+
jumped: {
|
|
59
|
+
index: number;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export declare function resolveIndex(target: number, direction: 1 | -1, rowCount: number, isSelectable: (index: number) => boolean): number | null;
|
|
63
|
+
export declare function createNavSearchStore(options?: {
|
|
64
|
+
keys?: readonly RowKey[];
|
|
65
|
+
deps?: NavSearchDeps;
|
|
66
|
+
}): import("@xstate/store").Store<NavSearchContext, NavSearchEvents, import("@xstate/store").ExtractEvents<NavSearchEmitted>>;
|
|
67
|
+
export type NavSearchStore = ReturnType<typeof createNavSearchStore>;
|
|
68
|
+
export declare const selectCursor: (ctx: NavSearchContext) => number | null;
|
|
69
|
+
export declare const selectRowKeys: (ctx: NavSearchContext) => readonly RowKey[];
|
|
70
|
+
export declare const selectSelectionAnchor: (ctx: NavSearchContext) => number | null;
|
|
71
|
+
export declare const selectToggledKeys: (ctx: NavSearchContext) => ReadonlySet<RowKey>;
|
|
72
|
+
export declare const selectSearchStatus: (ctx: NavSearchContext) => SearchStatus;
|
|
73
|
+
export declare const selectSearchActive: (ctx: NavSearchContext) => boolean;
|
|
74
|
+
export declare const selectSearchQuery: (ctx: NavSearchContext) => string;
|
|
75
|
+
export declare const selectMatches: (ctx: NavSearchContext) => readonly number[];
|
|
76
|
+
export declare const selectCurrentMatchIndex: (ctx: NavSearchContext) => number;
|
|
77
|
+
export declare function deriveSelection(ctx: NavSearchContext, mode: Mode): {
|
|
78
|
+
start: number;
|
|
79
|
+
end: number;
|
|
80
|
+
} | null;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=nav-search-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-search-store.d.ts","sourceRoot":"","sources":["../src/nav-search-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store";
|
|
2
|
+
export function resolveIndex(target, direction, rowCount, isSelectable) {
|
|
3
|
+
if (rowCount <= 0)
|
|
4
|
+
return null;
|
|
5
|
+
const max = rowCount - 1;
|
|
6
|
+
const clamped = Math.max(0, Math.min(target, max));
|
|
7
|
+
if (isSelectable(clamped))
|
|
8
|
+
return clamped;
|
|
9
|
+
for (let index = clamped + direction; direction === 1 ? index <= max : index >= 0; index += direction) {
|
|
10
|
+
if (isSelectable(index))
|
|
11
|
+
return index;
|
|
12
|
+
}
|
|
13
|
+
for (let index = clamped - direction; direction === 1 ? index >= 0 : index <= max; index -= direction) {
|
|
14
|
+
if (isSelectable(index))
|
|
15
|
+
return index;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const EMPTY_KEYS = [];
|
|
20
|
+
export function createNavSearchStore(options = {}) {
|
|
21
|
+
const deps = options.deps ?? { isSelectable: () => true };
|
|
22
|
+
const keys = options.keys ?? EMPTY_KEYS;
|
|
23
|
+
const resolve = (index, direction, count = keys.length) => resolveIndex(index, direction, count, deps.isSelectable);
|
|
24
|
+
return createStore({
|
|
25
|
+
context: {
|
|
26
|
+
rowKeys: keys,
|
|
27
|
+
cursor: resolve(0, 1),
|
|
28
|
+
selectionAnchor: null,
|
|
29
|
+
toggledKeys: new Set(),
|
|
30
|
+
search: {
|
|
31
|
+
status: "idle",
|
|
32
|
+
query: "",
|
|
33
|
+
committedQuery: "",
|
|
34
|
+
matches: [],
|
|
35
|
+
currentMatchIndex: 0,
|
|
36
|
+
preSearchMode: "cursor",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
on: {
|
|
40
|
+
rowsChanged: (ctx, event) => {
|
|
41
|
+
const previousKey = ctx.cursor === null ? undefined : ctx.rowKeys[ctx.cursor];
|
|
42
|
+
let cursor = ctx.cursor;
|
|
43
|
+
if (event.keys.length === 0)
|
|
44
|
+
cursor = null;
|
|
45
|
+
else if (event.preserveCursorByKey && previousKey !== undefined) {
|
|
46
|
+
const preserved = event.keys.indexOf(previousKey);
|
|
47
|
+
cursor =
|
|
48
|
+
preserved >= 0
|
|
49
|
+
? resolveIndex(preserved, 1, event.keys.length, deps.isSelectable)
|
|
50
|
+
: resolveIndex(Math.min(ctx.cursor, event.keys.length - 1), 1, event.keys.length, deps.isSelectable);
|
|
51
|
+
}
|
|
52
|
+
else if (cursor === null || cursor >= event.keys.length) {
|
|
53
|
+
cursor = resolveIndex(cursor === null ? 0 : event.keys.length - 1, cursor === null ? 1 : -1, event.keys.length, deps.isSelectable);
|
|
54
|
+
}
|
|
55
|
+
else if (!deps.isSelectable(cursor)) {
|
|
56
|
+
cursor = resolveIndex(cursor, 1, event.keys.length, deps.isSelectable);
|
|
57
|
+
}
|
|
58
|
+
const available = new Set(event.keys);
|
|
59
|
+
const filtered = new Set([...ctx.toggledKeys].filter((key) => available.has(key)));
|
|
60
|
+
const toggledKeys = filtered.size === ctx.toggledKeys.size ? ctx.toggledKeys : filtered;
|
|
61
|
+
return { ...ctx, rowKeys: event.keys, cursor, toggledKeys };
|
|
62
|
+
},
|
|
63
|
+
move: (ctx, event) => {
|
|
64
|
+
if (ctx.cursor === null)
|
|
65
|
+
return ctx;
|
|
66
|
+
const direction = event.delta < 0 ? -1 : 1;
|
|
67
|
+
const cursor = resolveIndex(ctx.cursor + event.delta, direction, ctx.rowKeys.length, deps.isSelectable);
|
|
68
|
+
return cursor === null || cursor === ctx.cursor ? ctx : { ...ctx, cursor };
|
|
69
|
+
},
|
|
70
|
+
jump: (ctx, event) => ({
|
|
71
|
+
...ctx,
|
|
72
|
+
cursor: resolveIndex(event.index, event.direction, ctx.rowKeys.length, deps.isSelectable),
|
|
73
|
+
}),
|
|
74
|
+
setCursor: (ctx, event) => {
|
|
75
|
+
const direction = ctx.cursor !== null && event.index < ctx.cursor ? -1 : 1;
|
|
76
|
+
const cursor = resolveIndex(event.index, direction, ctx.rowKeys.length, deps.isSelectable);
|
|
77
|
+
return cursor === null ? ctx : { ...ctx, cursor };
|
|
78
|
+
},
|
|
79
|
+
enterSelect: (ctx) => ({ ...ctx, selectionAnchor: ctx.cursor }),
|
|
80
|
+
cancelSelect: (ctx) => ctx.selectionAnchor === null ? ctx : { ...ctx, selectionAnchor: null },
|
|
81
|
+
toggleCurrent: (ctx) => toggle(ctx),
|
|
82
|
+
toggleAndMove: (ctx, event) => {
|
|
83
|
+
const toggled = toggle(ctx);
|
|
84
|
+
if (toggled.cursor === null)
|
|
85
|
+
return toggled;
|
|
86
|
+
const direction = event.delta < 0 ? -1 : 1;
|
|
87
|
+
const cursor = resolveIndex(toggled.cursor + event.delta, direction, toggled.rowKeys.length, deps.isSelectable);
|
|
88
|
+
return cursor === null ? toggled : { ...toggled, cursor };
|
|
89
|
+
},
|
|
90
|
+
searchStarted: (ctx, event) => ({
|
|
91
|
+
...ctx,
|
|
92
|
+
search: {
|
|
93
|
+
...ctx.search,
|
|
94
|
+
status: "editing",
|
|
95
|
+
query: "",
|
|
96
|
+
matches: [],
|
|
97
|
+
currentMatchIndex: 0,
|
|
98
|
+
preSearchMode: event.mode,
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
searchChanged: (ctx, event, enqueue) => {
|
|
102
|
+
const first = event.matches[0];
|
|
103
|
+
if (first !== undefined)
|
|
104
|
+
enqueue.emit.jumped({ index: first });
|
|
105
|
+
return {
|
|
106
|
+
...ctx,
|
|
107
|
+
cursor: first === undefined
|
|
108
|
+
? ctx.cursor
|
|
109
|
+
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
110
|
+
search: {
|
|
111
|
+
...ctx.search,
|
|
112
|
+
query: event.query,
|
|
113
|
+
matches: event.matches,
|
|
114
|
+
currentMatchIndex: 0,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
searchSubmitted: (ctx, _event, enqueue) => {
|
|
119
|
+
const first = ctx.search.matches[0];
|
|
120
|
+
if (first !== undefined)
|
|
121
|
+
enqueue.emit.jumped({ index: first });
|
|
122
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode });
|
|
123
|
+
return {
|
|
124
|
+
...ctx,
|
|
125
|
+
cursor: first === undefined
|
|
126
|
+
? ctx.cursor
|
|
127
|
+
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
128
|
+
search: {
|
|
129
|
+
...ctx.search,
|
|
130
|
+
status: "committed",
|
|
131
|
+
committedQuery: ctx.search.query,
|
|
132
|
+
currentMatchIndex: 0,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
searchCancelled: (ctx, _event, enqueue) => {
|
|
137
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode });
|
|
138
|
+
return {
|
|
139
|
+
...ctx,
|
|
140
|
+
search: {
|
|
141
|
+
...ctx.search,
|
|
142
|
+
status: "idle",
|
|
143
|
+
query: "",
|
|
144
|
+
committedQuery: "",
|
|
145
|
+
matches: [],
|
|
146
|
+
currentMatchIndex: 0,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
searchNext: (ctx, _event, enqueue) => searchStep(ctx, 1, enqueue),
|
|
151
|
+
searchPrevious: (ctx, _event, enqueue) => searchStep(ctx, -1, enqueue),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function toggle(ctx) {
|
|
156
|
+
if (ctx.cursor === null)
|
|
157
|
+
return ctx;
|
|
158
|
+
const key = ctx.rowKeys[ctx.cursor];
|
|
159
|
+
if (key === undefined)
|
|
160
|
+
return ctx;
|
|
161
|
+
const toggledKeys = new Set(ctx.toggledKeys);
|
|
162
|
+
if (toggledKeys.has(key))
|
|
163
|
+
toggledKeys.delete(key);
|
|
164
|
+
else
|
|
165
|
+
toggledKeys.add(key);
|
|
166
|
+
return { ...ctx, toggledKeys };
|
|
167
|
+
}
|
|
168
|
+
function searchStep(ctx, delta, enqueue) {
|
|
169
|
+
if (ctx.search.matches.length === 0)
|
|
170
|
+
return ctx;
|
|
171
|
+
const currentMatchIndex = (ctx.search.currentMatchIndex + delta + ctx.search.matches.length) % ctx.search.matches.length;
|
|
172
|
+
const cursor = ctx.search.matches[currentMatchIndex];
|
|
173
|
+
enqueue.emit.jumped({ index: cursor });
|
|
174
|
+
return { ...ctx, cursor, search: { ...ctx.search, currentMatchIndex } };
|
|
175
|
+
}
|
|
176
|
+
export const selectCursor = (ctx) => ctx.cursor;
|
|
177
|
+
export const selectRowKeys = (ctx) => ctx.rowKeys;
|
|
178
|
+
export const selectSelectionAnchor = (ctx) => ctx.selectionAnchor;
|
|
179
|
+
export const selectToggledKeys = (ctx) => ctx.toggledKeys;
|
|
180
|
+
export const selectSearchStatus = (ctx) => ctx.search.status;
|
|
181
|
+
export const selectSearchActive = (ctx) => ctx.search.status === "editing";
|
|
182
|
+
export const selectSearchQuery = (ctx) => ctx.search.status === "editing" ? ctx.search.query : ctx.search.committedQuery;
|
|
183
|
+
export const selectMatches = (ctx) => ctx.search.matches;
|
|
184
|
+
export const selectCurrentMatchIndex = (ctx) => ctx.search.currentMatchIndex;
|
|
185
|
+
export function deriveSelection(ctx, mode) {
|
|
186
|
+
return mode === "select" && ctx.selectionAnchor !== null && ctx.cursor !== null
|
|
187
|
+
? {
|
|
188
|
+
start: Math.min(ctx.selectionAnchor, ctx.cursor),
|
|
189
|
+
end: Math.max(ctx.selectionAnchor, ctx.cursor),
|
|
190
|
+
}
|
|
191
|
+
: null;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=nav-search-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-search-store.js","sourceRoot":"","sources":["../src/nav-search-store.ts"],"names":[],"mappings":"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"}
|
package/dist/search-hook.d.ts
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
+
import { type NavSearchStore, type RowKey } from "./nav-search-store.js";
|
|
1
2
|
export interface UseSearchOptions {
|
|
2
3
|
match: (query: string) => number[];
|
|
3
4
|
onJump: (index: number) => void;
|
|
4
|
-
/**
|
|
5
|
-
* Register the search commands (default true). `false` leaves the state
|
|
6
|
-
* inert: no `/`, `n`, `shift+n` or cancel command exists.
|
|
7
|
-
*/
|
|
8
5
|
enabled?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
* Values the searched content is derived from. Matches are memoized on the
|
|
11
|
-
* query, so a committed query goes stale when the content changes underneath
|
|
12
|
-
* it (streaming or reloaded documents). List the content here to re-match.
|
|
13
|
-
* The array length must be stable across renders.
|
|
14
|
-
*/
|
|
15
6
|
deps?: readonly unknown[];
|
|
16
7
|
}
|
|
17
8
|
export interface SearchState {
|
|
@@ -22,5 +13,13 @@ export interface SearchState {
|
|
|
22
13
|
currentMatchIndex: number;
|
|
23
14
|
submitSearch: () => void;
|
|
24
15
|
}
|
|
25
|
-
export declare function
|
|
16
|
+
export declare function useNavSearchStore(options: {
|
|
17
|
+
keys: readonly RowKey[];
|
|
18
|
+
isSelectable?: (index: number) => boolean;
|
|
19
|
+
preserveCursorByKey?: boolean;
|
|
20
|
+
}): NavSearchStore;
|
|
21
|
+
export declare function useSearchBindings(store: NavSearchStore, { match, onJump, enabled, deps, }: Omit<UseSearchOptions, "onJump"> & {
|
|
22
|
+
onJump?: (index: number) => void;
|
|
23
|
+
}): SearchState;
|
|
24
|
+
export declare function useSearch(options: UseSearchOptions): SearchState;
|
|
26
25
|
//# sourceMappingURL=search-hook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-hook.d.ts","sourceRoot":"","sources":["../src/search-hook.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IAClC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B
|
|
1
|
+
{"version":3,"file":"search-hook.d.ts","sourceRoot":"","sources":["../src/search-hook.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAA;AAE9B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IAClC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB;AAKD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IACzC,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,GAAG,cAAc,CAmBjB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,EACE,KAAK,EACL,MAAM,EACN,OAAc,EACd,IAAS,GACV,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACzE,WAAW,CA6Fb;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,WAAW,CAGhE"}
|
package/dist/search-hook.js
CHANGED
|
@@ -1,56 +1,59 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useSelector } from "@xstate/store-react";
|
|
2
3
|
import { useCommand, useMode, useSetMode } from "@tooee/commands";
|
|
3
|
-
|
|
4
|
-
const EMPTY_DEPS = [];
|
|
5
|
-
function sameDeps(left, right) {
|
|
6
|
-
if (left.length !== right.length)
|
|
7
|
-
return false;
|
|
8
|
-
return left.every((value, index) => Object.is(value, right[index]));
|
|
9
|
-
}
|
|
4
|
+
import { createNavSearchStore, selectCurrentMatchIndex, selectMatches, selectSearchActive, selectSearchQuery, } from "./nav-search-store.js";
|
|
10
5
|
const CURSOR_MODES = ["cursor"];
|
|
11
6
|
const ALL_MODES = ["cursor", "select", "insert"];
|
|
12
|
-
export function
|
|
7
|
+
export function useNavSearchStore(options) {
|
|
8
|
+
const depsRef = useRef({ isSelectable: options.isSelectable ?? (() => true) });
|
|
9
|
+
depsRef.current.isSelectable = options.isSelectable ?? (() => true);
|
|
10
|
+
const [store] = useState(() => createNavSearchStore({ keys: options.keys, deps: depsRef.current }));
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const currentKeys = store.getSnapshot().context.rowKeys;
|
|
13
|
+
const keysChanged = currentKeys.length !== options.keys.length ||
|
|
14
|
+
currentKeys.some((key, index) => !Object.is(key, options.keys[index]));
|
|
15
|
+
if (keysChanged) {
|
|
16
|
+
store.trigger.rowsChanged({
|
|
17
|
+
keys: options.keys,
|
|
18
|
+
preserveCursorByKey: options.preserveCursorByKey,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}, [store, options.keys, options.preserveCursorByKey]);
|
|
22
|
+
return store;
|
|
23
|
+
}
|
|
24
|
+
export function useSearchBindings(store, { match, onJump, enabled = true, deps = [], }) {
|
|
13
25
|
const mode = useMode();
|
|
14
26
|
const setMode = useSetMode();
|
|
15
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
16
|
-
const [searchActive, setSearchActive] = useState(false);
|
|
17
|
-
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
|
|
18
|
-
const [committedQuery, setCommittedQuery] = useState("");
|
|
19
|
-
const preSearchModeRef = useRef("cursor");
|
|
20
27
|
const matchRef = useRef(match);
|
|
21
28
|
matchRef.current = match;
|
|
22
29
|
const onJumpRef = useRef(onJump);
|
|
23
30
|
onJumpRef.current = onJump;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
matchingLines = cache.matches;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
matchingLines = matchRef.current(activeQuery);
|
|
40
|
-
cacheRef.current = { query: activeQuery, deps: [...deps], matches: matchingLines };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const matchingLinesRef = useRef(matchingLines);
|
|
44
|
-
matchingLinesRef.current = matchingLines;
|
|
45
|
-
// Imperatively set search query, reset match index, and jump to first match.
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const subscription = store.on("jumped", ({ index }) => onJumpRef.current?.(index));
|
|
33
|
+
return () => subscription.unsubscribe();
|
|
34
|
+
}, [store]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const subscription = store.on("restoreMode", ({ mode: restored }) => setMode(restored));
|
|
37
|
+
return () => subscription.unsubscribe();
|
|
38
|
+
}, [store, setMode]);
|
|
39
|
+
const searchQuery = useSelector(store, (snapshot) => selectSearchQuery(snapshot.context));
|
|
40
|
+
const searchActive = useSelector(store, (snapshot) => selectSearchActive(snapshot.context));
|
|
41
|
+
const matchingLines = useSelector(store, (snapshot) => selectMatches(snapshot.context));
|
|
42
|
+
const currentMatchIndex = useSelector(store, (snapshot) => selectCurrentMatchIndex(snapshot.context));
|
|
46
43
|
const updateSearchQuery = useCallback((query) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
store.trigger.searchChanged({ query, matches: query ? matchRef.current(query) : [] });
|
|
45
|
+
}, [store]);
|
|
46
|
+
// Re-match a committed query once when the caller-declared content changes.
|
|
47
|
+
const depsRef = useRef(deps);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (depsRef.current.every((value, index) => Object.is(value, deps[index])) &&
|
|
50
|
+
depsRef.current.length === deps.length)
|
|
51
|
+
return;
|
|
52
|
+
depsRef.current = deps;
|
|
53
|
+
const query = selectSearchQuery(store.getSnapshot().context);
|
|
54
|
+
if (query)
|
|
55
|
+
store.trigger.searchChanged({ query, matches: matchRef.current(query) });
|
|
56
|
+
}, [deps, store]);
|
|
54
57
|
useCommand({
|
|
55
58
|
id: "cursor-search-start",
|
|
56
59
|
title: "Search",
|
|
@@ -58,9 +61,7 @@ export function useSearch({ match, onJump, enabled = true, deps = EMPTY_DEPS, })
|
|
|
58
61
|
modes: CURSOR_MODES,
|
|
59
62
|
enabled,
|
|
60
63
|
handler: () => {
|
|
61
|
-
|
|
62
|
-
setSearchActive(true);
|
|
63
|
-
setSearchQuery("");
|
|
64
|
+
store.trigger.searchStarted({ mode });
|
|
64
65
|
setMode("insert");
|
|
65
66
|
},
|
|
66
67
|
});
|
|
@@ -70,20 +71,8 @@ export function useSearch({ match, onJump, enabled = true, deps = EMPTY_DEPS, })
|
|
|
70
71
|
hotkey: "n",
|
|
71
72
|
modes: CURSOR_MODES,
|
|
72
73
|
enabled,
|
|
73
|
-
when: () => !
|
|
74
|
-
handler: () => {
|
|
75
|
-
const matches = matchingLinesRef.current;
|
|
76
|
-
if (matches.length === 0)
|
|
77
|
-
return;
|
|
78
|
-
setCurrentMatchIndex((index) => {
|
|
79
|
-
const nextIndex = (index + 1) % matches.length;
|
|
80
|
-
const nextMatch = matches[nextIndex];
|
|
81
|
-
if (nextMatch != null) {
|
|
82
|
-
onJumpRef.current(nextMatch);
|
|
83
|
-
}
|
|
84
|
-
return nextIndex;
|
|
85
|
-
});
|
|
86
|
-
},
|
|
74
|
+
when: () => !selectSearchActive(store.getSnapshot().context),
|
|
75
|
+
handler: () => store.trigger.searchNext({}),
|
|
87
76
|
});
|
|
88
77
|
useCommand({
|
|
89
78
|
id: "cursor-search-prev",
|
|
@@ -91,20 +80,8 @@ export function useSearch({ match, onJump, enabled = true, deps = EMPTY_DEPS, })
|
|
|
91
80
|
hotkey: "shift+n",
|
|
92
81
|
modes: CURSOR_MODES,
|
|
93
82
|
enabled,
|
|
94
|
-
when: () => !
|
|
95
|
-
handler: () => {
|
|
96
|
-
const matches = matchingLinesRef.current;
|
|
97
|
-
if (matches.length === 0)
|
|
98
|
-
return;
|
|
99
|
-
setCurrentMatchIndex((index) => {
|
|
100
|
-
const nextIndex = (index - 1 + matches.length) % matches.length;
|
|
101
|
-
const nextMatch = matches[nextIndex];
|
|
102
|
-
if (nextMatch != null) {
|
|
103
|
-
onJumpRef.current(nextMatch);
|
|
104
|
-
}
|
|
105
|
-
return nextIndex;
|
|
106
|
-
});
|
|
107
|
-
},
|
|
83
|
+
when: () => !selectSearchActive(store.getSnapshot().context),
|
|
84
|
+
handler: () => store.trigger.searchPrevious({}),
|
|
108
85
|
});
|
|
109
86
|
useCommand({
|
|
110
87
|
id: "search-cancel",
|
|
@@ -112,32 +89,20 @@ export function useSearch({ match, onJump, enabled = true, deps = EMPTY_DEPS, })
|
|
|
112
89
|
hotkey: "escape",
|
|
113
90
|
modes: ALL_MODES,
|
|
114
91
|
enabled,
|
|
115
|
-
when: () =>
|
|
116
|
-
handler: () => {
|
|
117
|
-
setSearchActive(false);
|
|
118
|
-
setSearchQuery("");
|
|
119
|
-
setCommittedQuery("");
|
|
120
|
-
setCurrentMatchIndex(0);
|
|
121
|
-
setMode(preSearchModeRef.current);
|
|
122
|
-
},
|
|
92
|
+
when: () => selectSearchActive(store.getSnapshot().context),
|
|
93
|
+
handler: () => store.trigger.searchCancelled({}),
|
|
123
94
|
});
|
|
124
|
-
const submitSearch = useCallback(() => {
|
|
125
|
-
setCommittedQuery(searchQuery);
|
|
126
|
-
setSearchActive(false);
|
|
127
|
-
setCurrentMatchIndex(0);
|
|
128
|
-
const matches = searchQuery ? matchRef.current(searchQuery) : [];
|
|
129
|
-
if (matches[0] != null) {
|
|
130
|
-
onJumpRef.current(matches[0]);
|
|
131
|
-
}
|
|
132
|
-
setMode(preSearchModeRef.current);
|
|
133
|
-
}, [searchQuery, setMode]);
|
|
134
95
|
return {
|
|
135
96
|
searchQuery,
|
|
136
97
|
searchActive,
|
|
137
98
|
setSearchQuery: updateSearchQuery,
|
|
138
99
|
matchingLines,
|
|
139
100
|
currentMatchIndex,
|
|
140
|
-
submitSearch,
|
|
101
|
+
submitSearch: () => store.trigger.searchSubmitted({}),
|
|
141
102
|
};
|
|
142
103
|
}
|
|
104
|
+
export function useSearch(options) {
|
|
105
|
+
const store = useNavSearchStore({ keys: [] });
|
|
106
|
+
return useSearchBindings(store, options);
|
|
107
|
+
}
|
|
143
108
|
//# sourceMappingURL=search-hook.js.map
|
package/dist/search-hook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-hook.js","sourceRoot":"","sources":["../src/search-hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"search-hook.js","sourceRoot":"","sources":["../src/search-hook.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tooee/search",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Search hook, utilities, and SearchBar component for Tooee",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -35,8 +35,10 @@
|
|
|
35
35
|
"typecheck": "tsc --noEmit"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@tooee/commands": "0.2.
|
|
39
|
-
"@tooee/themes": "0.2.
|
|
38
|
+
"@tooee/commands": "0.2.3",
|
|
39
|
+
"@tooee/themes": "0.2.3",
|
|
40
|
+
"@xstate/store": "^4",
|
|
41
|
+
"@xstate/store-react": "^2"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@opentui/core": "^0.4.3",
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
export { useSearch } from "./search-hook.js"
|
|
2
|
+
export { useNavSearchStore, useSearchBindings } from "./search-hook.js"
|
|
3
|
+
export { createNavSearchStore } from "./nav-search-store.js"
|
|
4
|
+
export type {
|
|
5
|
+
NavSearchStore,
|
|
6
|
+
NavSearchContext,
|
|
7
|
+
NavSearchEvents,
|
|
8
|
+
RowKey,
|
|
9
|
+
} from "./nav-search-store.js"
|
|
10
|
+
export {
|
|
11
|
+
selectCursor,
|
|
12
|
+
selectRowKeys,
|
|
13
|
+
selectSelectionAnchor,
|
|
14
|
+
selectToggledKeys,
|
|
15
|
+
selectSearchStatus,
|
|
16
|
+
selectSearchActive,
|
|
17
|
+
selectSearchQuery,
|
|
18
|
+
selectMatches,
|
|
19
|
+
selectCurrentMatchIndex,
|
|
20
|
+
deriveSelection,
|
|
21
|
+
resolveIndex,
|
|
22
|
+
} from "./nav-search-store.js"
|
|
2
23
|
export type { UseSearchOptions, SearchState } from "./search-hook.js"
|
|
3
24
|
export { findMatchingLines } from "./search.js"
|
|
4
25
|
export { SearchBar } from "./SearchBar.js"
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store"
|
|
2
|
+
import type { Mode } from "@tooee/commands"
|
|
3
|
+
|
|
4
|
+
export type RowKey = string | number | bigint
|
|
5
|
+
export type SearchStatus = "idle" | "editing" | "committed"
|
|
6
|
+
|
|
7
|
+
export interface NavSearchContext {
|
|
8
|
+
rowKeys: readonly RowKey[]
|
|
9
|
+
cursor: number | null
|
|
10
|
+
selectionAnchor: number | null
|
|
11
|
+
toggledKeys: ReadonlySet<RowKey>
|
|
12
|
+
search: {
|
|
13
|
+
status: SearchStatus
|
|
14
|
+
query: string
|
|
15
|
+
committedQuery: string
|
|
16
|
+
matches: readonly number[]
|
|
17
|
+
currentMatchIndex: number
|
|
18
|
+
preSearchMode: Mode
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NavSearchDeps {
|
|
23
|
+
isSelectable(index: number): boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type NavSearchEvents = {
|
|
27
|
+
rowsChanged: { keys: readonly RowKey[]; preserveCursorByKey?: boolean }
|
|
28
|
+
move: { delta: number }
|
|
29
|
+
jump: { index: number; direction: 1 | -1 }
|
|
30
|
+
setCursor: { index: number }
|
|
31
|
+
enterSelect: {}
|
|
32
|
+
cancelSelect: {}
|
|
33
|
+
toggleCurrent: {}
|
|
34
|
+
toggleAndMove: { delta: number }
|
|
35
|
+
searchStarted: { mode: Mode }
|
|
36
|
+
searchChanged: { query: string; matches: readonly number[] }
|
|
37
|
+
searchSubmitted: {}
|
|
38
|
+
searchCancelled: {}
|
|
39
|
+
searchNext: {}
|
|
40
|
+
searchPrevious: {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type NavSearchEmitted = {
|
|
44
|
+
restoreMode: { mode: Mode }
|
|
45
|
+
jumped: { index: number }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveIndex(
|
|
49
|
+
target: number,
|
|
50
|
+
direction: 1 | -1,
|
|
51
|
+
rowCount: number,
|
|
52
|
+
isSelectable: (index: number) => boolean,
|
|
53
|
+
): number | null {
|
|
54
|
+
if (rowCount <= 0) return null
|
|
55
|
+
const max = rowCount - 1
|
|
56
|
+
const clamped = Math.max(0, Math.min(target, max))
|
|
57
|
+
if (isSelectable(clamped)) return clamped
|
|
58
|
+
for (
|
|
59
|
+
let index = clamped + direction;
|
|
60
|
+
direction === 1 ? index <= max : index >= 0;
|
|
61
|
+
index += direction
|
|
62
|
+
) {
|
|
63
|
+
if (isSelectable(index)) return index
|
|
64
|
+
}
|
|
65
|
+
for (
|
|
66
|
+
let index = clamped - direction;
|
|
67
|
+
direction === 1 ? index >= 0 : index <= max;
|
|
68
|
+
index -= direction
|
|
69
|
+
) {
|
|
70
|
+
if (isSelectable(index)) return index
|
|
71
|
+
}
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const EMPTY_KEYS: readonly RowKey[] = []
|
|
76
|
+
|
|
77
|
+
export function createNavSearchStore(
|
|
78
|
+
options: {
|
|
79
|
+
keys?: readonly RowKey[]
|
|
80
|
+
deps?: NavSearchDeps
|
|
81
|
+
} = {},
|
|
82
|
+
) {
|
|
83
|
+
const deps = options.deps ?? { isSelectable: () => true }
|
|
84
|
+
const keys = options.keys ?? EMPTY_KEYS
|
|
85
|
+
const resolve = (index: number, direction: 1 | -1, count = keys.length) =>
|
|
86
|
+
resolveIndex(index, direction, count, deps.isSelectable)
|
|
87
|
+
|
|
88
|
+
return createStore<NavSearchContext, NavSearchEvents, NavSearchEmitted>({
|
|
89
|
+
context: {
|
|
90
|
+
rowKeys: keys,
|
|
91
|
+
cursor: resolve(0, 1),
|
|
92
|
+
selectionAnchor: null,
|
|
93
|
+
toggledKeys: new Set(),
|
|
94
|
+
search: {
|
|
95
|
+
status: "idle",
|
|
96
|
+
query: "",
|
|
97
|
+
committedQuery: "",
|
|
98
|
+
matches: [],
|
|
99
|
+
currentMatchIndex: 0,
|
|
100
|
+
preSearchMode: "cursor",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
on: {
|
|
104
|
+
rowsChanged: (ctx, event) => {
|
|
105
|
+
const previousKey = ctx.cursor === null ? undefined : ctx.rowKeys[ctx.cursor]
|
|
106
|
+
let cursor = ctx.cursor
|
|
107
|
+
if (event.keys.length === 0) cursor = null
|
|
108
|
+
else if (event.preserveCursorByKey && previousKey !== undefined) {
|
|
109
|
+
const preserved = event.keys.indexOf(previousKey)
|
|
110
|
+
cursor =
|
|
111
|
+
preserved >= 0
|
|
112
|
+
? resolveIndex(preserved, 1, event.keys.length, deps.isSelectable)
|
|
113
|
+
: resolveIndex(
|
|
114
|
+
Math.min(ctx.cursor!, event.keys.length - 1),
|
|
115
|
+
1,
|
|
116
|
+
event.keys.length,
|
|
117
|
+
deps.isSelectable,
|
|
118
|
+
)
|
|
119
|
+
} else if (cursor === null || cursor >= event.keys.length) {
|
|
120
|
+
cursor = resolveIndex(
|
|
121
|
+
cursor === null ? 0 : event.keys.length - 1,
|
|
122
|
+
cursor === null ? 1 : -1,
|
|
123
|
+
event.keys.length,
|
|
124
|
+
deps.isSelectable,
|
|
125
|
+
)
|
|
126
|
+
} else if (!deps.isSelectable(cursor)) {
|
|
127
|
+
cursor = resolveIndex(cursor, 1, event.keys.length, deps.isSelectable)
|
|
128
|
+
}
|
|
129
|
+
const available = new Set(event.keys)
|
|
130
|
+
const filtered = new Set([...ctx.toggledKeys].filter((key) => available.has(key)))
|
|
131
|
+
const toggledKeys = filtered.size === ctx.toggledKeys.size ? ctx.toggledKeys : filtered
|
|
132
|
+
return { ...ctx, rowKeys: event.keys, cursor, toggledKeys }
|
|
133
|
+
},
|
|
134
|
+
move: (ctx, event) => {
|
|
135
|
+
if (ctx.cursor === null) return ctx
|
|
136
|
+
const direction: 1 | -1 = event.delta < 0 ? -1 : 1
|
|
137
|
+
const cursor = resolveIndex(
|
|
138
|
+
ctx.cursor + event.delta,
|
|
139
|
+
direction,
|
|
140
|
+
ctx.rowKeys.length,
|
|
141
|
+
deps.isSelectable,
|
|
142
|
+
)
|
|
143
|
+
return cursor === null || cursor === ctx.cursor ? ctx : { ...ctx, cursor }
|
|
144
|
+
},
|
|
145
|
+
jump: (ctx, event) => ({
|
|
146
|
+
...ctx,
|
|
147
|
+
cursor: resolveIndex(event.index, event.direction, ctx.rowKeys.length, deps.isSelectable),
|
|
148
|
+
}),
|
|
149
|
+
setCursor: (ctx, event) => {
|
|
150
|
+
const direction: 1 | -1 = ctx.cursor !== null && event.index < ctx.cursor ? -1 : 1
|
|
151
|
+
const cursor = resolveIndex(event.index, direction, ctx.rowKeys.length, deps.isSelectable)
|
|
152
|
+
return cursor === null ? ctx : { ...ctx, cursor }
|
|
153
|
+
},
|
|
154
|
+
enterSelect: (ctx) => ({ ...ctx, selectionAnchor: ctx.cursor }),
|
|
155
|
+
cancelSelect: (ctx) =>
|
|
156
|
+
ctx.selectionAnchor === null ? ctx : { ...ctx, selectionAnchor: null },
|
|
157
|
+
toggleCurrent: (ctx) => toggle(ctx),
|
|
158
|
+
toggleAndMove: (ctx, event) => {
|
|
159
|
+
const toggled = toggle(ctx)
|
|
160
|
+
if (toggled.cursor === null) return toggled
|
|
161
|
+
const direction: 1 | -1 = event.delta < 0 ? -1 : 1
|
|
162
|
+
const cursor = resolveIndex(
|
|
163
|
+
toggled.cursor + event.delta,
|
|
164
|
+
direction,
|
|
165
|
+
toggled.rowKeys.length,
|
|
166
|
+
deps.isSelectable,
|
|
167
|
+
)
|
|
168
|
+
return cursor === null ? toggled : { ...toggled, cursor }
|
|
169
|
+
},
|
|
170
|
+
searchStarted: (ctx, event) => ({
|
|
171
|
+
...ctx,
|
|
172
|
+
search: {
|
|
173
|
+
...ctx.search,
|
|
174
|
+
status: "editing",
|
|
175
|
+
query: "",
|
|
176
|
+
matches: [],
|
|
177
|
+
currentMatchIndex: 0,
|
|
178
|
+
preSearchMode: event.mode,
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
searchChanged: (ctx, event, enqueue) => {
|
|
182
|
+
const first = event.matches[0]
|
|
183
|
+
if (first !== undefined) enqueue.emit.jumped({ index: first })
|
|
184
|
+
return {
|
|
185
|
+
...ctx,
|
|
186
|
+
cursor:
|
|
187
|
+
first === undefined
|
|
188
|
+
? ctx.cursor
|
|
189
|
+
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
190
|
+
search: {
|
|
191
|
+
...ctx.search,
|
|
192
|
+
query: event.query,
|
|
193
|
+
matches: event.matches,
|
|
194
|
+
currentMatchIndex: 0,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
searchSubmitted: (ctx, _event, enqueue) => {
|
|
199
|
+
const first = ctx.search.matches[0]
|
|
200
|
+
if (first !== undefined) enqueue.emit.jumped({ index: first })
|
|
201
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode })
|
|
202
|
+
return {
|
|
203
|
+
...ctx,
|
|
204
|
+
cursor:
|
|
205
|
+
first === undefined
|
|
206
|
+
? ctx.cursor
|
|
207
|
+
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
208
|
+
search: {
|
|
209
|
+
...ctx.search,
|
|
210
|
+
status: "committed",
|
|
211
|
+
committedQuery: ctx.search.query,
|
|
212
|
+
currentMatchIndex: 0,
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
searchCancelled: (ctx, _event, enqueue) => {
|
|
217
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode })
|
|
218
|
+
return {
|
|
219
|
+
...ctx,
|
|
220
|
+
search: {
|
|
221
|
+
...ctx.search,
|
|
222
|
+
status: "idle",
|
|
223
|
+
query: "",
|
|
224
|
+
committedQuery: "",
|
|
225
|
+
matches: [],
|
|
226
|
+
currentMatchIndex: 0,
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
searchNext: (ctx, _event, enqueue) => searchStep(ctx, 1, enqueue),
|
|
231
|
+
searchPrevious: (ctx, _event, enqueue) => searchStep(ctx, -1, enqueue),
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function toggle(ctx: NavSearchContext): NavSearchContext {
|
|
237
|
+
if (ctx.cursor === null) return ctx
|
|
238
|
+
const key = ctx.rowKeys[ctx.cursor]
|
|
239
|
+
if (key === undefined) return ctx
|
|
240
|
+
const toggledKeys = new Set(ctx.toggledKeys)
|
|
241
|
+
if (toggledKeys.has(key)) toggledKeys.delete(key)
|
|
242
|
+
else toggledKeys.add(key)
|
|
243
|
+
return { ...ctx, toggledKeys }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function searchStep(
|
|
247
|
+
ctx: NavSearchContext,
|
|
248
|
+
delta: 1 | -1,
|
|
249
|
+
enqueue: { emit: { jumped: (event: { index: number }) => void } },
|
|
250
|
+
): NavSearchContext {
|
|
251
|
+
if (ctx.search.matches.length === 0) return ctx
|
|
252
|
+
const currentMatchIndex =
|
|
253
|
+
(ctx.search.currentMatchIndex + delta + ctx.search.matches.length) % ctx.search.matches.length
|
|
254
|
+
const cursor = ctx.search.matches[currentMatchIndex]!
|
|
255
|
+
enqueue.emit.jumped({ index: cursor })
|
|
256
|
+
return { ...ctx, cursor, search: { ...ctx.search, currentMatchIndex } }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export type NavSearchStore = ReturnType<typeof createNavSearchStore>
|
|
260
|
+
|
|
261
|
+
export const selectCursor = (ctx: NavSearchContext) => ctx.cursor
|
|
262
|
+
export const selectRowKeys = (ctx: NavSearchContext) => ctx.rowKeys
|
|
263
|
+
export const selectSelectionAnchor = (ctx: NavSearchContext) => ctx.selectionAnchor
|
|
264
|
+
export const selectToggledKeys = (ctx: NavSearchContext) => ctx.toggledKeys
|
|
265
|
+
export const selectSearchStatus = (ctx: NavSearchContext) => ctx.search.status
|
|
266
|
+
export const selectSearchActive = (ctx: NavSearchContext) => ctx.search.status === "editing"
|
|
267
|
+
export const selectSearchQuery = (ctx: NavSearchContext) =>
|
|
268
|
+
ctx.search.status === "editing" ? ctx.search.query : ctx.search.committedQuery
|
|
269
|
+
export const selectMatches = (ctx: NavSearchContext) => ctx.search.matches
|
|
270
|
+
export const selectCurrentMatchIndex = (ctx: NavSearchContext) => ctx.search.currentMatchIndex
|
|
271
|
+
export function deriveSelection(ctx: NavSearchContext, mode: Mode) {
|
|
272
|
+
return mode === "select" && ctx.selectionAnchor !== null && ctx.cursor !== null
|
|
273
|
+
? {
|
|
274
|
+
start: Math.min(ctx.selectionAnchor, ctx.cursor),
|
|
275
|
+
end: Math.max(ctx.selectionAnchor, ctx.cursor),
|
|
276
|
+
}
|
|
277
|
+
: null
|
|
278
|
+
}
|
package/src/search-hook.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from "react"
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
2
|
+
import { useSelector } from "@xstate/store-react"
|
|
2
3
|
import { useCommand, useMode, useSetMode, type Mode } from "@tooee/commands"
|
|
4
|
+
import {
|
|
5
|
+
createNavSearchStore,
|
|
6
|
+
selectCurrentMatchIndex,
|
|
7
|
+
selectMatches,
|
|
8
|
+
selectSearchActive,
|
|
9
|
+
selectSearchQuery,
|
|
10
|
+
type NavSearchDeps,
|
|
11
|
+
type NavSearchStore,
|
|
12
|
+
type RowKey,
|
|
13
|
+
} from "./nav-search-store.js"
|
|
3
14
|
|
|
4
15
|
export interface UseSearchOptions {
|
|
5
16
|
match: (query: string) => number[]
|
|
6
17
|
onJump: (index: number) => void
|
|
7
|
-
/**
|
|
8
|
-
* Register the search commands (default true). `false` leaves the state
|
|
9
|
-
* inert: no `/`, `n`, `shift+n` or cancel command exists.
|
|
10
|
-
*/
|
|
11
18
|
enabled?: boolean
|
|
12
|
-
/**
|
|
13
|
-
* Values the searched content is derived from. Matches are memoized on the
|
|
14
|
-
* query, so a committed query goes stale when the content changes underneath
|
|
15
|
-
* it (streaming or reloaded documents). List the content here to re-match.
|
|
16
|
-
* The array length must be stable across renders.
|
|
17
|
-
*/
|
|
18
19
|
deps?: readonly unknown[]
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -27,74 +28,87 @@ export interface SearchState {
|
|
|
27
28
|
submitSearch: () => void
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const EMPTY: number[] = []
|
|
31
|
-
const EMPTY_DEPS: readonly unknown[] = []
|
|
32
|
-
|
|
33
|
-
interface MatchCache {
|
|
34
|
-
query: string
|
|
35
|
-
deps: readonly unknown[]
|
|
36
|
-
matches: number[]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function sameDeps(left: readonly unknown[], right: readonly unknown[]): boolean {
|
|
40
|
-
if (left.length !== right.length) return false
|
|
41
|
-
return left.every((value, index) => Object.is(value, right[index]))
|
|
42
|
-
}
|
|
43
31
|
const CURSOR_MODES: Mode[] = ["cursor"]
|
|
44
32
|
const ALL_MODES: Mode[] = ["cursor", "select", "insert"]
|
|
45
33
|
|
|
46
|
-
export function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
export function useNavSearchStore(options: {
|
|
35
|
+
keys: readonly RowKey[]
|
|
36
|
+
isSelectable?: (index: number) => boolean
|
|
37
|
+
preserveCursorByKey?: boolean
|
|
38
|
+
}): NavSearchStore {
|
|
39
|
+
const depsRef = useRef<NavSearchDeps>({ isSelectable: options.isSelectable ?? (() => true) })
|
|
40
|
+
depsRef.current.isSelectable = options.isSelectable ?? (() => true)
|
|
41
|
+
const [store] = useState(() =>
|
|
42
|
+
createNavSearchStore({ keys: options.keys, deps: depsRef.current }),
|
|
43
|
+
)
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const currentKeys = store.getSnapshot().context.rowKeys
|
|
46
|
+
const keysChanged =
|
|
47
|
+
currentKeys.length !== options.keys.length ||
|
|
48
|
+
currentKeys.some((key, index) => !Object.is(key, options.keys[index]))
|
|
49
|
+
if (keysChanged) {
|
|
50
|
+
store.trigger.rowsChanged({
|
|
51
|
+
keys: options.keys,
|
|
52
|
+
preserveCursorByKey: options.preserveCursorByKey,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}, [store, options.keys, options.preserveCursorByKey])
|
|
56
|
+
return store
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useSearchBindings(
|
|
60
|
+
store: NavSearchStore,
|
|
61
|
+
{
|
|
62
|
+
match,
|
|
63
|
+
onJump,
|
|
64
|
+
enabled = true,
|
|
65
|
+
deps = [],
|
|
66
|
+
}: Omit<UseSearchOptions, "onJump"> & { onJump?: (index: number) => void },
|
|
67
|
+
): SearchState {
|
|
52
68
|
const mode = useMode()
|
|
53
69
|
const setMode = useSetMode()
|
|
54
|
-
|
|
55
|
-
const [searchQuery, setSearchQuery] = useState("")
|
|
56
|
-
const [searchActive, setSearchActive] = useState(false)
|
|
57
|
-
const [currentMatchIndex, setCurrentMatchIndex] = useState(0)
|
|
58
|
-
const [committedQuery, setCommittedQuery] = useState("")
|
|
59
|
-
const preSearchModeRef = useRef<Mode>("cursor")
|
|
60
|
-
|
|
61
70
|
const matchRef = useRef(match)
|
|
62
71
|
matchRef.current = match
|
|
63
|
-
|
|
64
72
|
const onJumpRef = useRef(onJump)
|
|
65
73
|
onJumpRef.current = onJump
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const subscription = store.on("jumped", ({ index }) => onJumpRef.current?.(index))
|
|
77
|
+
return () => subscription.unsubscribe()
|
|
78
|
+
}, [store])
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const subscription = store.on("restoreMode", ({ mode: restored }) => setMode(restored))
|
|
81
|
+
return () => subscription.unsubscribe()
|
|
82
|
+
}, [store, setMode])
|
|
83
|
+
|
|
84
|
+
const searchQuery = useSelector(store, (snapshot) => selectSearchQuery(snapshot.context))
|
|
85
|
+
const searchActive = useSelector(store, (snapshot) => selectSearchActive(snapshot.context))
|
|
86
|
+
const matchingLines = useSelector(store, (snapshot) =>
|
|
87
|
+
selectMatches(snapshot.context),
|
|
88
|
+
) as number[]
|
|
89
|
+
const currentMatchIndex = useSelector(store, (snapshot) =>
|
|
90
|
+
selectCurrentMatchIndex(snapshot.context),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const updateSearchQuery = useCallback(
|
|
94
|
+
(query: string) => {
|
|
95
|
+
store.trigger.searchChanged({ query, matches: query ? matchRef.current(query) : [] })
|
|
96
|
+
},
|
|
97
|
+
[store],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Re-match a committed query once when the caller-declared content changes.
|
|
101
|
+
const depsRef = useRef(deps)
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (
|
|
104
|
+
depsRef.current.every((value, index) => Object.is(value, deps[index])) &&
|
|
105
|
+
depsRef.current.length === deps.length
|
|
106
|
+
)
|
|
107
|
+
return
|
|
108
|
+
depsRef.current = deps
|
|
109
|
+
const query = selectSearchQuery(store.getSnapshot().context)
|
|
110
|
+
if (query) store.trigger.searchChanged({ query, matches: matchRef.current(query) })
|
|
111
|
+
}, [deps, store])
|
|
98
112
|
|
|
99
113
|
useCommand({
|
|
100
114
|
id: "cursor-search-start",
|
|
@@ -103,90 +117,49 @@ export function useSearch({
|
|
|
103
117
|
modes: CURSOR_MODES,
|
|
104
118
|
enabled,
|
|
105
119
|
handler: () => {
|
|
106
|
-
|
|
107
|
-
setSearchActive(true)
|
|
108
|
-
setSearchQuery("")
|
|
120
|
+
store.trigger.searchStarted({ mode })
|
|
109
121
|
setMode("insert")
|
|
110
122
|
},
|
|
111
123
|
})
|
|
112
|
-
|
|
113
124
|
useCommand({
|
|
114
125
|
id: "cursor-search-next",
|
|
115
126
|
title: "Next match",
|
|
116
127
|
hotkey: "n",
|
|
117
128
|
modes: CURSOR_MODES,
|
|
118
129
|
enabled,
|
|
119
|
-
when: () => !
|
|
120
|
-
handler: () => {
|
|
121
|
-
const matches = matchingLinesRef.current
|
|
122
|
-
if (matches.length === 0) return
|
|
123
|
-
|
|
124
|
-
setCurrentMatchIndex((index) => {
|
|
125
|
-
const nextIndex = (index + 1) % matches.length
|
|
126
|
-
const nextMatch = matches[nextIndex]
|
|
127
|
-
if (nextMatch != null) {
|
|
128
|
-
onJumpRef.current(nextMatch)
|
|
129
|
-
}
|
|
130
|
-
return nextIndex
|
|
131
|
-
})
|
|
132
|
-
},
|
|
130
|
+
when: () => !selectSearchActive(store.getSnapshot().context),
|
|
131
|
+
handler: () => store.trigger.searchNext({}),
|
|
133
132
|
})
|
|
134
|
-
|
|
135
133
|
useCommand({
|
|
136
134
|
id: "cursor-search-prev",
|
|
137
135
|
title: "Previous match",
|
|
138
136
|
hotkey: "shift+n",
|
|
139
137
|
modes: CURSOR_MODES,
|
|
140
138
|
enabled,
|
|
141
|
-
when: () => !
|
|
142
|
-
handler: () => {
|
|
143
|
-
const matches = matchingLinesRef.current
|
|
144
|
-
if (matches.length === 0) return
|
|
145
|
-
|
|
146
|
-
setCurrentMatchIndex((index) => {
|
|
147
|
-
const nextIndex = (index - 1 + matches.length) % matches.length
|
|
148
|
-
const nextMatch = matches[nextIndex]
|
|
149
|
-
if (nextMatch != null) {
|
|
150
|
-
onJumpRef.current(nextMatch)
|
|
151
|
-
}
|
|
152
|
-
return nextIndex
|
|
153
|
-
})
|
|
154
|
-
},
|
|
139
|
+
when: () => !selectSearchActive(store.getSnapshot().context),
|
|
140
|
+
handler: () => store.trigger.searchPrevious({}),
|
|
155
141
|
})
|
|
156
|
-
|
|
157
142
|
useCommand({
|
|
158
143
|
id: "search-cancel",
|
|
159
144
|
title: "Cancel search",
|
|
160
145
|
hotkey: "escape",
|
|
161
146
|
modes: ALL_MODES,
|
|
162
147
|
enabled,
|
|
163
|
-
when: () =>
|
|
164
|
-
handler: () => {
|
|
165
|
-
setSearchActive(false)
|
|
166
|
-
setSearchQuery("")
|
|
167
|
-
setCommittedQuery("")
|
|
168
|
-
setCurrentMatchIndex(0)
|
|
169
|
-
setMode(preSearchModeRef.current)
|
|
170
|
-
},
|
|
148
|
+
when: () => selectSearchActive(store.getSnapshot().context),
|
|
149
|
+
handler: () => store.trigger.searchCancelled({}),
|
|
171
150
|
})
|
|
172
151
|
|
|
173
|
-
const submitSearch = useCallback(() => {
|
|
174
|
-
setCommittedQuery(searchQuery)
|
|
175
|
-
setSearchActive(false)
|
|
176
|
-
setCurrentMatchIndex(0)
|
|
177
|
-
const matches = searchQuery ? matchRef.current(searchQuery) : []
|
|
178
|
-
if (matches[0] != null) {
|
|
179
|
-
onJumpRef.current(matches[0])
|
|
180
|
-
}
|
|
181
|
-
setMode(preSearchModeRef.current)
|
|
182
|
-
}, [searchQuery, setMode])
|
|
183
|
-
|
|
184
152
|
return {
|
|
185
153
|
searchQuery,
|
|
186
154
|
searchActive,
|
|
187
155
|
setSearchQuery: updateSearchQuery,
|
|
188
156
|
matchingLines,
|
|
189
157
|
currentMatchIndex,
|
|
190
|
-
submitSearch,
|
|
158
|
+
submitSearch: () => store.trigger.searchSubmitted({}),
|
|
191
159
|
}
|
|
192
160
|
}
|
|
161
|
+
|
|
162
|
+
export function useSearch(options: UseSearchOptions): SearchState {
|
|
163
|
+
const store = useNavSearchStore({ keys: [] })
|
|
164
|
+
return useSearchBindings(store, options)
|
|
165
|
+
}
|