@tooee/search 0.2.3 → 0.3.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/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/nav-search-store.d.ts +21 -15
- package/dist/nav-search-store.d.ts.map +1 -1
- package/dist/nav-search-store.js +122 -105
- package/dist/nav-search-store.js.map +1 -1
- package/dist/{SearchBar.d.ts → search-bar.d.ts} +2 -2
- package/dist/search-bar.d.ts.map +1 -0
- package/dist/{SearchBar.js → search-bar.js} +12 -10
- package/dist/search-bar.js.map +1 -0
- package/dist/search-hook.d.ts +6 -6
- package/dist/search-hook.d.ts.map +1 -1
- package/dist/search-hook.js +55 -35
- package/dist/search-hook.js.map +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +6 -5
- package/dist/search.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +9 -9
- package/src/nav-search-store.ts +217 -180
- package/src/{SearchBar.tsx → search-bar.tsx} +20 -20
- package/src/search-hook.ts +111 -93
- package/src/search.ts +15 -13
- package/dist/SearchBar.d.ts.map +0 -1
- package/dist/SearchBar.js.map +0 -1
package/src/nav-search-store.ts
CHANGED
|
@@ -1,186 +1,227 @@
|
|
|
1
|
-
import { createStore } from "@xstate/store"
|
|
2
|
-
import type { Mode } from "@tooee/commands"
|
|
1
|
+
import { createStore } from "@xstate/store";
|
|
2
|
+
import type { Mode } from "@tooee/commands";
|
|
3
3
|
|
|
4
|
-
export type RowKey = string | number | bigint
|
|
5
|
-
export type SearchStatus = "idle" | "editing" | "committed"
|
|
4
|
+
export type RowKey = string | number | bigint;
|
|
5
|
+
export type SearchStatus = "idle" | "editing" | "committed";
|
|
6
6
|
|
|
7
7
|
export interface NavSearchContext {
|
|
8
|
-
rowKeys: readonly RowKey[]
|
|
9
|
-
cursor: number | null
|
|
10
|
-
selectionAnchor: number | null
|
|
11
|
-
toggledKeys: ReadonlySet<RowKey
|
|
8
|
+
rowKeys: readonly RowKey[];
|
|
9
|
+
cursor: number | null;
|
|
10
|
+
selectionAnchor: number | null;
|
|
11
|
+
toggledKeys: ReadonlySet<RowKey>;
|
|
12
12
|
search: {
|
|
13
|
-
status: SearchStatus
|
|
14
|
-
query: string
|
|
15
|
-
committedQuery: string
|
|
16
|
-
matches: readonly number[]
|
|
17
|
-
currentMatchIndex: number
|
|
18
|
-
preSearchMode: Mode
|
|
19
|
-
}
|
|
13
|
+
status: SearchStatus;
|
|
14
|
+
query: string;
|
|
15
|
+
committedQuery: string;
|
|
16
|
+
matches: readonly number[];
|
|
17
|
+
currentMatchIndex: number;
|
|
18
|
+
preSearchMode: Mode;
|
|
19
|
+
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface NavSearchDeps {
|
|
23
|
-
isSelectable(index: number)
|
|
23
|
+
isSelectable: (index: number) => boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
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:
|
|
26
|
+
interface NavSearchEventDefinitions {
|
|
27
|
+
rowsChanged: { keys: readonly RowKey[]; preserveCursorByKey?: boolean };
|
|
28
|
+
move: { delta: number };
|
|
29
|
+
jump: { index: number; direction: 1 | -1 };
|
|
30
|
+
setCursor: { index: number };
|
|
31
|
+
enterSelect: Record<never, never>;
|
|
32
|
+
cancelSelect: Record<never, never>;
|
|
33
|
+
toggleCurrent: Record<never, never>;
|
|
34
|
+
toggleAndMove: { delta: number };
|
|
35
|
+
searchStarted: { mode: Mode };
|
|
36
|
+
searchChanged: { query: string; matches: readonly number[] };
|
|
37
|
+
searchSubmitted: Record<never, never>;
|
|
38
|
+
searchCancelled: Record<never, never>;
|
|
39
|
+
searchNext: Record<never, never>;
|
|
40
|
+
searchPrevious: Record<never, never>;
|
|
41
41
|
}
|
|
42
|
+
export type NavSearchEvents = {
|
|
43
|
+
[EventName in keyof NavSearchEventDefinitions]: NavSearchEventDefinitions[EventName];
|
|
44
|
+
};
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
restoreMode: { mode: Mode }
|
|
45
|
-
jumped: { index: number }
|
|
46
|
+
interface NavSearchEmittedDefinitions {
|
|
47
|
+
restoreMode: { mode: Mode };
|
|
48
|
+
jumped: { index: number };
|
|
46
49
|
}
|
|
50
|
+
type NavSearchEmitted = {
|
|
51
|
+
[EventName in keyof NavSearchEmittedDefinitions]: NavSearchEmittedDefinitions[EventName];
|
|
52
|
+
};
|
|
47
53
|
|
|
48
|
-
export function resolveIndex(
|
|
54
|
+
export const resolveIndex = function resolveIndex(
|
|
49
55
|
target: number,
|
|
50
56
|
direction: 1 | -1,
|
|
51
57
|
rowCount: number,
|
|
52
58
|
isSelectable: (index: number) => boolean,
|
|
53
59
|
): number | null {
|
|
54
|
-
if (rowCount <= 0)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
if (rowCount <= 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const max = rowCount - 1;
|
|
64
|
+
const clamped = Math.max(0, Math.min(target, max));
|
|
65
|
+
if (isSelectable(clamped)) {
|
|
66
|
+
return clamped;
|
|
67
|
+
}
|
|
58
68
|
for (
|
|
59
69
|
let index = clamped + direction;
|
|
60
70
|
direction === 1 ? index <= max : index >= 0;
|
|
61
71
|
index += direction
|
|
62
72
|
) {
|
|
63
|
-
if (isSelectable(index))
|
|
73
|
+
if (isSelectable(index)) {
|
|
74
|
+
return index;
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
for (
|
|
66
78
|
let index = clamped - direction;
|
|
67
79
|
direction === 1 ? index >= 0 : index <= max;
|
|
68
80
|
index -= direction
|
|
69
81
|
) {
|
|
70
|
-
if (isSelectable(index))
|
|
82
|
+
if (isSelectable(index)) {
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
71
85
|
}
|
|
72
|
-
return null
|
|
73
|
-
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const toggle = function toggle(ctx: NavSearchContext): NavSearchContext {
|
|
90
|
+
if (ctx.cursor === null) {
|
|
91
|
+
return ctx;
|
|
92
|
+
}
|
|
93
|
+
const key = ctx.rowKeys[ctx.cursor];
|
|
94
|
+
if (key === undefined) {
|
|
95
|
+
return ctx;
|
|
96
|
+
}
|
|
97
|
+
const toggledKeys = new Set(ctx.toggledKeys);
|
|
98
|
+
if (toggledKeys.has(key)) {
|
|
99
|
+
toggledKeys.delete(key);
|
|
100
|
+
} else {
|
|
101
|
+
toggledKeys.add(key);
|
|
102
|
+
}
|
|
103
|
+
return { ...ctx, toggledKeys };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const searchStep = function searchStep(
|
|
107
|
+
ctx: NavSearchContext,
|
|
108
|
+
delta: 1 | -1,
|
|
109
|
+
enqueue: { emit: { jumped: (event: { index: number }) => void } },
|
|
110
|
+
): NavSearchContext {
|
|
111
|
+
if (ctx.search.matches.length === 0) {
|
|
112
|
+
return ctx;
|
|
113
|
+
}
|
|
114
|
+
const currentMatchIndex =
|
|
115
|
+
(ctx.search.currentMatchIndex + delta + ctx.search.matches.length) % ctx.search.matches.length;
|
|
116
|
+
const cursor = ctx.search.matches[currentMatchIndex];
|
|
117
|
+
enqueue.emit.jumped({ index: cursor });
|
|
118
|
+
return { ...ctx, cursor, search: { ...ctx.search, currentMatchIndex } };
|
|
119
|
+
};
|
|
74
120
|
|
|
75
|
-
const EMPTY_KEYS: readonly RowKey[] = []
|
|
121
|
+
const EMPTY_KEYS: readonly RowKey[] = [];
|
|
76
122
|
|
|
77
|
-
export function createNavSearchStore(
|
|
123
|
+
export const createNavSearchStore = function createNavSearchStore(
|
|
78
124
|
options: {
|
|
79
|
-
keys?: readonly RowKey[]
|
|
80
|
-
deps?: NavSearchDeps
|
|
125
|
+
keys?: readonly RowKey[];
|
|
126
|
+
deps?: NavSearchDeps;
|
|
81
127
|
} = {},
|
|
82
128
|
) {
|
|
83
|
-
const deps = options.deps ?? { isSelectable: () => true }
|
|
84
|
-
const keys = options.keys ?? EMPTY_KEYS
|
|
129
|
+
const deps = options.deps ?? { isSelectable: () => true };
|
|
130
|
+
const keys = options.keys ?? EMPTY_KEYS;
|
|
85
131
|
const resolve = (index: number, direction: 1 | -1, count = keys.length) =>
|
|
86
|
-
resolveIndex(index, direction, count, deps.isSelectable)
|
|
132
|
+
resolveIndex(index, direction, count, deps.isSelectable);
|
|
87
133
|
|
|
88
134
|
return createStore<NavSearchContext, NavSearchEvents, NavSearchEmitted>({
|
|
89
135
|
context: {
|
|
90
|
-
rowKeys: keys,
|
|
91
136
|
cursor: resolve(0, 1),
|
|
92
|
-
|
|
93
|
-
toggledKeys: new Set(),
|
|
137
|
+
rowKeys: keys,
|
|
94
138
|
search: {
|
|
95
|
-
status: "idle",
|
|
96
|
-
query: "",
|
|
97
139
|
committedQuery: "",
|
|
98
|
-
matches: [],
|
|
99
140
|
currentMatchIndex: 0,
|
|
141
|
+
matches: [],
|
|
100
142
|
preSearchMode: "cursor",
|
|
143
|
+
query: "",
|
|
144
|
+
status: "idle",
|
|
101
145
|
},
|
|
146
|
+
selectionAnchor: null,
|
|
147
|
+
toggledKeys: new Set(),
|
|
102
148
|
},
|
|
103
149
|
on: {
|
|
150
|
+
cancelSelect: (ctx) =>
|
|
151
|
+
ctx.selectionAnchor === null ? ctx : { ...ctx, selectionAnchor: null },
|
|
152
|
+
enterSelect: (ctx) => ({ ...ctx, selectionAnchor: ctx.cursor }),
|
|
153
|
+
jump: (ctx, event) => ({
|
|
154
|
+
...ctx,
|
|
155
|
+
cursor: resolveIndex(event.index, event.direction, ctx.rowKeys.length, deps.isSelectable),
|
|
156
|
+
}),
|
|
157
|
+
move: (ctx, event) => {
|
|
158
|
+
if (ctx.cursor === null) {
|
|
159
|
+
return ctx;
|
|
160
|
+
}
|
|
161
|
+
const direction: 1 | -1 = event.delta < 0 ? -1 : 1;
|
|
162
|
+
const cursor = resolveIndex(
|
|
163
|
+
ctx.cursor + event.delta,
|
|
164
|
+
direction,
|
|
165
|
+
ctx.rowKeys.length,
|
|
166
|
+
deps.isSelectable,
|
|
167
|
+
);
|
|
168
|
+
return cursor === null || cursor === ctx.cursor ? ctx : { ...ctx, cursor };
|
|
169
|
+
},
|
|
104
170
|
rowsChanged: (ctx, event) => {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
171
|
+
const previousCursor = ctx.cursor;
|
|
172
|
+
const previousKey = previousCursor === null ? undefined : ctx.rowKeys[previousCursor];
|
|
173
|
+
let { cursor } = ctx;
|
|
174
|
+
if (event.keys.length === 0) {
|
|
175
|
+
cursor = null;
|
|
176
|
+
} else if (
|
|
177
|
+
event.preserveCursorByKey === true &&
|
|
178
|
+
previousCursor !== null &&
|
|
179
|
+
previousKey !== undefined
|
|
180
|
+
) {
|
|
181
|
+
const preserved = event.keys.indexOf(previousKey);
|
|
110
182
|
cursor =
|
|
111
|
-
preserved
|
|
112
|
-
? resolveIndex(
|
|
113
|
-
|
|
114
|
-
Math.min(ctx.cursor!, event.keys.length - 1),
|
|
183
|
+
preserved === -1
|
|
184
|
+
? resolveIndex(
|
|
185
|
+
Math.min(previousCursor, event.keys.length - 1),
|
|
115
186
|
1,
|
|
116
187
|
event.keys.length,
|
|
117
188
|
deps.isSelectable,
|
|
118
189
|
)
|
|
190
|
+
: resolveIndex(preserved, 1, event.keys.length, deps.isSelectable);
|
|
119
191
|
} else if (cursor === null || cursor >= event.keys.length) {
|
|
120
192
|
cursor = resolveIndex(
|
|
121
193
|
cursor === null ? 0 : event.keys.length - 1,
|
|
122
194
|
cursor === null ? 1 : -1,
|
|
123
195
|
event.keys.length,
|
|
124
196
|
deps.isSelectable,
|
|
125
|
-
)
|
|
197
|
+
);
|
|
126
198
|
} else if (!deps.isSelectable(cursor)) {
|
|
127
|
-
cursor = resolveIndex(cursor, 1, event.keys.length, deps.isSelectable)
|
|
199
|
+
cursor = resolveIndex(cursor, 1, event.keys.length, deps.isSelectable);
|
|
128
200
|
}
|
|
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,
|
|
201
|
+
const available = new Set(event.keys);
|
|
202
|
+
const filtered = new Set([...ctx.toggledKeys].filter((key) => available.has(key)));
|
|
203
|
+
const toggledKeys = filtered.size === ctx.toggledKeys.size ? ctx.toggledKeys : filtered;
|
|
204
|
+
return { ...ctx, cursor, rowKeys: event.keys, toggledKeys };
|
|
133
205
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 }
|
|
206
|
+
searchCancelled: (ctx, _event, enqueue) => {
|
|
207
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode });
|
|
208
|
+
return {
|
|
209
|
+
...ctx,
|
|
210
|
+
search: {
|
|
211
|
+
...ctx.search,
|
|
212
|
+
committedQuery: "",
|
|
213
|
+
currentMatchIndex: 0,
|
|
214
|
+
matches: [],
|
|
215
|
+
query: "",
|
|
216
|
+
status: "idle",
|
|
217
|
+
},
|
|
218
|
+
};
|
|
169
219
|
},
|
|
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
220
|
searchChanged: (ctx, event, enqueue) => {
|
|
182
|
-
const first = event.matches
|
|
183
|
-
if (first !== undefined)
|
|
221
|
+
const [first] = event.matches;
|
|
222
|
+
if (first !== undefined) {
|
|
223
|
+
enqueue.emit.jumped({ index: first });
|
|
224
|
+
}
|
|
184
225
|
return {
|
|
185
226
|
...ctx,
|
|
186
227
|
cursor:
|
|
@@ -189,16 +230,31 @@ export function createNavSearchStore(
|
|
|
189
230
|
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
190
231
|
search: {
|
|
191
232
|
...ctx.search,
|
|
192
|
-
query: event.query,
|
|
193
|
-
matches: event.matches,
|
|
194
233
|
currentMatchIndex: 0,
|
|
234
|
+
matches: event.matches,
|
|
235
|
+
query: event.query,
|
|
195
236
|
},
|
|
196
|
-
}
|
|
237
|
+
};
|
|
197
238
|
},
|
|
239
|
+
searchNext: (ctx, _event, enqueue) => searchStep(ctx, 1, enqueue),
|
|
240
|
+
searchPrevious: (ctx, _event, enqueue) => searchStep(ctx, -1, enqueue),
|
|
241
|
+
searchStarted: (ctx, event) => ({
|
|
242
|
+
...ctx,
|
|
243
|
+
search: {
|
|
244
|
+
...ctx.search,
|
|
245
|
+
currentMatchIndex: 0,
|
|
246
|
+
matches: [],
|
|
247
|
+
preSearchMode: event.mode,
|
|
248
|
+
query: "",
|
|
249
|
+
status: "editing",
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
198
252
|
searchSubmitted: (ctx, _event, enqueue) => {
|
|
199
|
-
const first = ctx.search.matches
|
|
200
|
-
if (first !== undefined)
|
|
201
|
-
|
|
253
|
+
const [first] = ctx.search.matches;
|
|
254
|
+
if (first !== undefined) {
|
|
255
|
+
enqueue.emit.jumped({ index: first });
|
|
256
|
+
}
|
|
257
|
+
enqueue.emit.restoreMode({ mode: ctx.search.preSearchMode });
|
|
202
258
|
return {
|
|
203
259
|
...ctx,
|
|
204
260
|
cursor:
|
|
@@ -207,72 +263,53 @@ export function createNavSearchStore(
|
|
|
207
263
|
: resolveIndex(first, 1, ctx.rowKeys.length, deps.isSelectable),
|
|
208
264
|
search: {
|
|
209
265
|
...ctx.search,
|
|
210
|
-
status: "committed",
|
|
211
266
|
committedQuery: ctx.search.query,
|
|
212
267
|
currentMatchIndex: 0,
|
|
268
|
+
status: "committed",
|
|
213
269
|
},
|
|
214
|
-
}
|
|
270
|
+
};
|
|
215
271
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
matches: [],
|
|
226
|
-
currentMatchIndex: 0,
|
|
227
|
-
},
|
|
272
|
+
setCursor: (ctx, event) => {
|
|
273
|
+
const direction: 1 | -1 = ctx.cursor !== null && event.index < ctx.cursor ? -1 : 1;
|
|
274
|
+
const cursor = resolveIndex(event.index, direction, ctx.rowKeys.length, deps.isSelectable);
|
|
275
|
+
return cursor === null ? ctx : { ...ctx, cursor };
|
|
276
|
+
},
|
|
277
|
+
toggleAndMove: (ctx, event) => {
|
|
278
|
+
const toggled = toggle(ctx);
|
|
279
|
+
if (toggled.cursor === null) {
|
|
280
|
+
return toggled;
|
|
228
281
|
}
|
|
282
|
+
const direction: 1 | -1 = event.delta < 0 ? -1 : 1;
|
|
283
|
+
const cursor = resolveIndex(
|
|
284
|
+
toggled.cursor + event.delta,
|
|
285
|
+
direction,
|
|
286
|
+
toggled.rowKeys.length,
|
|
287
|
+
deps.isSelectable,
|
|
288
|
+
);
|
|
289
|
+
return cursor === null ? toggled : { ...toggled, cursor };
|
|
229
290
|
},
|
|
230
|
-
|
|
231
|
-
searchPrevious: (ctx, _event, enqueue) => searchStep(ctx, -1, enqueue),
|
|
291
|
+
toggleCurrent: (ctx) => toggle(ctx),
|
|
232
292
|
},
|
|
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
|
-
}
|
|
293
|
+
});
|
|
294
|
+
};
|
|
258
295
|
|
|
259
|
-
export type NavSearchStore = ReturnType<typeof createNavSearchStore
|
|
296
|
+
export type NavSearchStore = ReturnType<typeof createNavSearchStore>;
|
|
260
297
|
|
|
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"
|
|
298
|
+
export const selectCursor = (ctx: NavSearchContext) => ctx.cursor;
|
|
299
|
+
export const selectRowKeys = (ctx: NavSearchContext) => ctx.rowKeys;
|
|
300
|
+
export const selectSelectionAnchor = (ctx: NavSearchContext) => ctx.selectionAnchor;
|
|
301
|
+
export const selectToggledKeys = (ctx: NavSearchContext) => ctx.toggledKeys;
|
|
302
|
+
export const selectSearchStatus = (ctx: NavSearchContext) => ctx.search.status;
|
|
303
|
+
export const selectSearchActive = (ctx: NavSearchContext) => ctx.search.status === "editing";
|
|
267
304
|
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) {
|
|
305
|
+
ctx.search.status === "editing" ? ctx.search.query : ctx.search.committedQuery;
|
|
306
|
+
export const selectMatches = (ctx: NavSearchContext) => ctx.search.matches;
|
|
307
|
+
export const selectCurrentMatchIndex = (ctx: NavSearchContext) => ctx.search.currentMatchIndex;
|
|
308
|
+
export const deriveSelection = function deriveSelection(ctx: NavSearchContext, mode: Mode) {
|
|
272
309
|
return mode === "select" && ctx.selectionAnchor !== null && ctx.cursor !== null
|
|
273
310
|
? {
|
|
274
|
-
start: Math.min(ctx.selectionAnchor, ctx.cursor),
|
|
275
311
|
end: Math.max(ctx.selectionAnchor, ctx.cursor),
|
|
312
|
+
start: Math.min(ctx.selectionAnchor, ctx.cursor),
|
|
276
313
|
}
|
|
277
|
-
: null
|
|
278
|
-
}
|
|
314
|
+
: null;
|
|
315
|
+
};
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { useTheme } from "@tooee/themes"
|
|
1
|
+
import { useTheme } from "@tooee/themes";
|
|
2
2
|
|
|
3
3
|
export interface SearchBarProps {
|
|
4
|
-
query: string
|
|
5
|
-
onQueryChange: (query: string) => void
|
|
6
|
-
onSubmit: () => void
|
|
7
|
-
onCancel: () => void
|
|
8
|
-
matchCount?: number
|
|
9
|
-
currentMatch?: number
|
|
4
|
+
query: string;
|
|
5
|
+
onQueryChange: (query: string) => void;
|
|
6
|
+
onSubmit: () => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
matchCount?: number;
|
|
9
|
+
currentMatch?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function SearchBar({
|
|
12
|
+
export const SearchBar = function SearchBar({
|
|
13
13
|
query,
|
|
14
14
|
onQueryChange,
|
|
15
15
|
onSubmit,
|
|
16
16
|
onCancel: _onCancel,
|
|
17
17
|
matchCount,
|
|
18
18
|
currentMatch,
|
|
19
|
-
}: SearchBarProps) {
|
|
20
|
-
const { theme } = useTheme()
|
|
19
|
+
}: SearchBarProps): React.ReactNode {
|
|
20
|
+
const { theme } = useTheme();
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
let matchDisplay = "";
|
|
23
|
+
if (matchCount !== undefined && matchCount > 0) {
|
|
24
|
+
matchDisplay = `${(currentMatch ?? 0) + 1}/${matchCount}`;
|
|
25
|
+
} else if (matchCount === 0 && query.length > 0) {
|
|
26
|
+
matchDisplay = "No matches";
|
|
27
|
+
}
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
30
|
<box
|
|
31
31
|
style={{
|
|
32
|
+
backgroundColor: theme.backgroundPanel,
|
|
32
33
|
flexDirection: "row",
|
|
33
34
|
flexShrink: 0,
|
|
34
|
-
backgroundColor: theme.backgroundPanel,
|
|
35
35
|
paddingLeft: 1,
|
|
36
36
|
paddingRight: 1,
|
|
37
37
|
}}
|
|
@@ -46,10 +46,10 @@ export function SearchBar({
|
|
|
46
46
|
focusedBackgroundColor="transparent"
|
|
47
47
|
textColor={theme.text}
|
|
48
48
|
cursorColor={theme.accent}
|
|
49
|
-
cursorStyle={{ style: "line"
|
|
49
|
+
cursorStyle={{ blinking: true, style: "line" }}
|
|
50
50
|
style={{ flexGrow: 1 }}
|
|
51
51
|
/>
|
|
52
52
|
{matchDisplay ? <text content={` ${matchDisplay}`} style={{ fg: theme.textMuted }} /> : null}
|
|
53
53
|
</box>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
54
|
+
);
|
|
55
|
+
};
|