@tutti-os/workspace-file-reference 0.0.13 → 0.0.15

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.
@@ -1,6 +1,10 @@
1
1
  import {
2
+ SOURCE_ROOT_NODE_ID,
3
+ appendReferencePage,
4
+ nodeRefKey,
5
+ sortReferenceNodes,
2
6
  uniqueWorkspaceFileReferences
3
- } from "./chunk-PAR2R2G5.js";
7
+ } from "./chunk-HRC2MZ5H.js";
4
8
 
5
9
  // src/react/internal/reference/WorkspaceFileReferencePickerState.ts
6
10
  var workspaceFileReferenceDefaultExpandedDepth = 4;
@@ -158,7 +162,15 @@ var defaultDirectoryPath = "/";
158
162
  var defaultSearchDebounceMs = 180;
159
163
  function createWorkspaceFileReferencePickerController(input) {
160
164
  const searchDebounceMs = input.searchDebounceMs ?? defaultSearchDebounceMs;
161
- let browseSequence = 0;
165
+ let nextBrowseSeq = 0;
166
+ const latestBrowseSeqByKey = /* @__PURE__ */ new Map();
167
+ const stampBrowse = (seqKey) => {
168
+ const sequence = ++nextBrowseSeq;
169
+ latestBrowseSeqByKey.set(seqKey, sequence);
170
+ return sequence;
171
+ };
172
+ const isBrowseStale = (seqKey, sequence) => !retained || latestBrowseSeqByKey.get(seqKey) !== sequence;
173
+ const REVEAL_SEQ_KEY = "\0reveal";
162
174
  let previewObjectUrl = null;
163
175
  let previewSequence = 0;
164
176
  let retained = false;
@@ -219,7 +231,7 @@ function createWorkspaceFileReferencePickerController(input) {
219
231
  searchAbortController = null;
220
232
  };
221
233
  const cancelCurrentBrowse = () => {
222
- browseSequence += 1;
234
+ latestBrowseSeqByKey.clear();
223
235
  };
224
236
  const cancelCurrentPreview = () => {
225
237
  previewSequence += 1;
@@ -306,7 +318,8 @@ function createWorkspaceFileReferencePickerController(input) {
306
318
  if (normalizedRoot && snapshot.directoryStateByPath[normalizedRoot]?.loaded) {
307
319
  return;
308
320
  }
309
- const sequence = ++browseSequence;
321
+ const seqKey = normalizedRoot ?? "";
322
+ const sequence = stampBrowse(seqKey);
310
323
  setSnapshot({
311
324
  browseError: null,
312
325
  isBrowseLoading: true
@@ -319,7 +332,7 @@ function createWorkspaceFileReferencePickerController(input) {
319
332
  prefetchDepth: 4,
320
333
  workspaceId: input.workspaceId
321
334
  });
322
- if (!retained || sequence !== browseSequence) {
335
+ if (isBrowseStale(seqKey, sequence)) {
323
336
  return;
324
337
  }
325
338
  setSnapshot({
@@ -334,7 +347,7 @@ function createWorkspaceFileReferencePickerController(input) {
334
347
  return;
335
348
  }
336
349
  const listing = await loadDirectoryListing(activeBrowseRootPath);
337
- if (!retained || sequence !== browseSequence || !listing) {
350
+ if (isBrowseStale(seqKey, sequence) || !listing) {
338
351
  return;
339
352
  }
340
353
  setSnapshot((current) => ({
@@ -352,7 +365,7 @@ function createWorkspaceFileReferencePickerController(input) {
352
365
  isBrowseLoading: false
353
366
  }));
354
367
  } catch (error) {
355
- if (!retained || sequence !== browseSequence) {
368
+ if (isBrowseStale(seqKey, sequence)) {
356
369
  return;
357
370
  }
358
371
  setSnapshot({
@@ -369,7 +382,7 @@ function createWorkspaceFileReferencePickerController(input) {
369
382
  if (!retained || snapshot.directoryStateByPath[folderKey]?.loaded || snapshot.directoryStateByPath[folderKey]?.loading) {
370
383
  return;
371
384
  }
372
- const sequence = ++browseSequence;
385
+ const sequence = stampBrowse(folderKey);
373
386
  setSnapshot((current) => ({
374
387
  ...current,
375
388
  directoryStateByPath: {
@@ -384,7 +397,7 @@ function createWorkspaceFileReferencePickerController(input) {
384
397
  }));
385
398
  try {
386
399
  const listing = await loadDirectoryListing(folderKey);
387
- if (!retained || sequence !== browseSequence || !listing) {
400
+ if (isBrowseStale(folderKey, sequence) || !listing) {
388
401
  return;
389
402
  }
390
403
  setSnapshot((current) => ({
@@ -400,7 +413,7 @@ function createWorkspaceFileReferencePickerController(input) {
400
413
  }
401
414
  }));
402
415
  } catch {
403
- if (!retained || sequence !== browseSequence) {
416
+ if (isBrowseStale(folderKey, sequence)) {
404
417
  return;
405
418
  }
406
419
  setSnapshot((current) => ({
@@ -538,7 +551,7 @@ function createWorkspaceFileReferencePickerController(input) {
538
551
  });
539
552
  return null;
540
553
  }
541
- const sequence = ++browseSequence;
554
+ const sequence = stampBrowse(REVEAL_SEQ_KEY);
542
555
  const loadedDirectories = {};
543
556
  const expandedDirectories = {};
544
557
  const directoryState = (path) => loadedDirectories[path] ?? snapshot.directoryStateByPath[path];
@@ -569,7 +582,7 @@ function createWorkspaceFileReferencePickerController(input) {
569
582
  loading: false
570
583
  };
571
584
  }
572
- if (!retained || sequence !== browseSequence) {
585
+ if (isBrowseStale(REVEAL_SEQ_KEY, sequence)) {
573
586
  return null;
574
587
  }
575
588
  setSnapshot((current) => ({
@@ -888,6 +901,638 @@ function useWorkspaceFileReferencePickerView({
888
901
  };
889
902
  }
890
903
 
904
+ // src/react/internal/reference/referenceSourcePickerController.ts
905
+ import { proxy as proxy2 } from "valtio/vanilla";
906
+ var ROOT_CHILDREN_KEY = nodeRefKey({
907
+ sourceId: "",
908
+ nodeId: SOURCE_ROOT_NODE_ID
909
+ });
910
+ var defaultSearchDebounceMs2 = 180;
911
+ var SEARCH_PAGE_SIZE = 30;
912
+ var SEARCH_MAX_LIMIT = 200;
913
+ function emptyTabState(sourceId) {
914
+ return {
915
+ sourceId,
916
+ expandedKeys: {},
917
+ childrenByKey: {},
918
+ mode: "browse",
919
+ searchQuery: "",
920
+ searchFilters: [],
921
+ searchScopeNodeId: null,
922
+ searchEntries: [],
923
+ searchNextCursor: null,
924
+ searchLimit: 0,
925
+ searchHasMore: false,
926
+ isSearchLoadingMore: false,
927
+ isSearchLoading: false,
928
+ searchError: null
929
+ };
930
+ }
931
+ function emptyChildrenState() {
932
+ return {
933
+ entries: [],
934
+ nextCursor: null,
935
+ loaded: false,
936
+ loading: false,
937
+ error: null
938
+ };
939
+ }
940
+ function createReferenceSourcePickerController(input) {
941
+ const { aggregator, scope } = input;
942
+ const searchDebounceMs = input.searchDebounceMs ?? defaultSearchDebounceMs2;
943
+ let retained = false;
944
+ let tabsReady = Promise.resolve();
945
+ let tabsSequence = 0;
946
+ let nextBrowseSeq = 0;
947
+ const latestBrowseSeqByKey = /* @__PURE__ */ new Map();
948
+ let searchSequence = 0;
949
+ let searchAbortController = null;
950
+ let searchTimer = null;
951
+ let snapshot = {
952
+ isLoadingTabs: false,
953
+ tabsError: null,
954
+ tabs: [],
955
+ activeSourceId: null,
956
+ bySource: {},
957
+ selection: []
958
+ };
959
+ const store = proxy2(snapshot);
960
+ const setSnapshot = (update) => {
961
+ const next = typeof update === "function" ? update(snapshot) : { ...snapshot, ...update };
962
+ if (next === snapshot) {
963
+ return;
964
+ }
965
+ snapshot = next;
966
+ Object.assign(store, next);
967
+ };
968
+ const updateTab = (sourceId, updater) => {
969
+ setSnapshot((current) => {
970
+ const existing = current.bySource[sourceId] ?? emptyTabState(sourceId);
971
+ const nextTab = updater(existing);
972
+ if (nextTab === existing) {
973
+ return current;
974
+ }
975
+ return {
976
+ ...current,
977
+ bySource: { ...current.bySource, [sourceId]: nextTab }
978
+ };
979
+ });
980
+ };
981
+ const childrenKeyForNode = (node) => node ? nodeRefKey(node.ref) : ROOT_CHILDREN_KEY;
982
+ const setChildrenState = (sourceId, key, patch) => {
983
+ updateTab(sourceId, (tab) => {
984
+ const current = tab.childrenByKey[key] ?? emptyChildrenState();
985
+ return {
986
+ ...tab,
987
+ childrenByKey: {
988
+ ...tab.childrenByKey,
989
+ [key]: { ...current, ...patch }
990
+ }
991
+ };
992
+ });
993
+ };
994
+ const loadChildren = async (sourceId, node, options) => {
995
+ if (!retained) {
996
+ return;
997
+ }
998
+ const key = childrenKeyForNode(node);
999
+ const tab = snapshot.bySource[sourceId];
1000
+ const existing = tab?.childrenByKey[key];
1001
+ const cursor = options.append ? existing?.nextCursor ?? null : null;
1002
+ if (existing?.loading) {
1003
+ return;
1004
+ }
1005
+ if (options.append && !cursor) {
1006
+ return;
1007
+ }
1008
+ const seqKey = `${sourceId}\0${key}`;
1009
+ const sequence = ++nextBrowseSeq;
1010
+ latestBrowseSeqByKey.set(seqKey, sequence);
1011
+ setChildrenState(sourceId, key, { loading: true, error: null });
1012
+ try {
1013
+ const result = await aggregator.listChildren(
1014
+ scope,
1015
+ node ? node.ref : { sourceId, nodeId: SOURCE_ROOT_NODE_ID },
1016
+ { cursor }
1017
+ );
1018
+ if (!retained || latestBrowseSeqByKey.get(seqKey) !== sequence) {
1019
+ return;
1020
+ }
1021
+ const prior = snapshot.bySource[sourceId]?.childrenByKey[key]?.entries ?? [];
1022
+ const entries = options.append ? appendReferencePage(prior, result.entries) : result.ordered ? [...result.entries] : sortReferenceNodes(result.entries);
1023
+ setChildrenState(sourceId, key, {
1024
+ entries,
1025
+ nextCursor: result.nextCursor ?? null,
1026
+ loaded: true,
1027
+ loading: false,
1028
+ error: null
1029
+ });
1030
+ } catch (error) {
1031
+ if (!retained || latestBrowseSeqByKey.get(seqKey) !== sequence) {
1032
+ return;
1033
+ }
1034
+ setChildrenState(sourceId, key, {
1035
+ loading: false,
1036
+ error: normalizeError(error, "load children failed")
1037
+ });
1038
+ }
1039
+ };
1040
+ const ensureRootLoaded = (sourceId) => {
1041
+ const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
1042
+ if (root?.loaded || root?.loading) {
1043
+ return;
1044
+ }
1045
+ void loadChildren(sourceId, null, { append: false });
1046
+ };
1047
+ const loadTabs = async () => {
1048
+ if (!retained) {
1049
+ return;
1050
+ }
1051
+ const sequence = ++tabsSequence;
1052
+ setSnapshot({ isLoadingTabs: true, tabsError: null });
1053
+ try {
1054
+ const tabs = await aggregator.listSources(scope);
1055
+ if (!retained || sequence !== tabsSequence) {
1056
+ return;
1057
+ }
1058
+ const activeSourceId = snapshot.activeSourceId && tabs.some((tab) => tab.sourceId === snapshot.activeSourceId) ? snapshot.activeSourceId : tabs[0]?.sourceId ?? null;
1059
+ setSnapshot((current) => ({
1060
+ ...current,
1061
+ isLoadingTabs: false,
1062
+ tabs,
1063
+ activeSourceId,
1064
+ bySource: Object.fromEntries(
1065
+ tabs.map((tab) => [
1066
+ tab.sourceId,
1067
+ current.bySource[tab.sourceId] ?? emptyTabState(tab.sourceId)
1068
+ ])
1069
+ )
1070
+ }));
1071
+ if (activeSourceId) {
1072
+ ensureRootLoaded(activeSourceId);
1073
+ }
1074
+ } catch (error) {
1075
+ if (!retained || sequence !== tabsSequence) {
1076
+ return;
1077
+ }
1078
+ setSnapshot({
1079
+ isLoadingTabs: false,
1080
+ tabsError: normalizeError(error, "load reference sources failed")
1081
+ });
1082
+ }
1083
+ };
1084
+ const clearSearchTimer = () => {
1085
+ if (searchTimer !== null) {
1086
+ clearTimeout(searchTimer);
1087
+ searchTimer = null;
1088
+ }
1089
+ };
1090
+ const cancelSearch = () => {
1091
+ clearSearchTimer();
1092
+ searchSequence += 1;
1093
+ searchAbortController?.abort();
1094
+ searchAbortController = null;
1095
+ };
1096
+ const runSearch = async (sourceId, query, filters, scopeNodeId, limit, loadingMore) => {
1097
+ if (!retained) {
1098
+ return;
1099
+ }
1100
+ const sequence = ++searchSequence;
1101
+ searchAbortController?.abort();
1102
+ const abortController = new AbortController();
1103
+ searchAbortController = abortController;
1104
+ updateTab(sourceId, (tab) => ({
1105
+ ...tab,
1106
+ ...loadingMore ? { isSearchLoadingMore: true } : { isSearchLoading: true },
1107
+ searchError: null
1108
+ }));
1109
+ try {
1110
+ const result = await aggregator.search(scope, sourceId, {
1111
+ query,
1112
+ limit,
1113
+ signal: abortController.signal,
1114
+ ...filters.length > 0 ? { filters } : {},
1115
+ ...scopeNodeId == null ? {} : { withinNodeId: scopeNodeId }
1116
+ });
1117
+ if (!retained || sequence !== searchSequence) {
1118
+ return;
1119
+ }
1120
+ updateTab(sourceId, (tab) => ({
1121
+ ...tab,
1122
+ isSearchLoading: false,
1123
+ isSearchLoadingMore: false,
1124
+ searchEntries: sortReferenceNodes(result.entries),
1125
+ searchNextCursor: result.nextCursor ?? null,
1126
+ searchLimit: limit,
1127
+ // 本页返回数达到请求上限、且未触全局上限 → 认为可能还有更多(增长式分页启发式)。
1128
+ searchHasMore: result.entries.length >= limit && limit < SEARCH_MAX_LIMIT,
1129
+ searchError: null
1130
+ }));
1131
+ } catch (error) {
1132
+ if (isAbortError2(error) || !retained || sequence !== searchSequence) {
1133
+ return;
1134
+ }
1135
+ updateTab(sourceId, (tab) => ({
1136
+ ...tab,
1137
+ isSearchLoading: false,
1138
+ isSearchLoadingMore: false,
1139
+ // 加载更多失败时保留已有结果,仅新查询失败才清空。
1140
+ ...loadingMore ? {} : { searchEntries: [], searchHasMore: false },
1141
+ searchError: normalizeError(error, "reference search failed")
1142
+ }));
1143
+ } finally {
1144
+ if (sequence === searchSequence) {
1145
+ searchAbortController = null;
1146
+ }
1147
+ }
1148
+ };
1149
+ const collectFolderFiles = async (folder) => {
1150
+ const files = [];
1151
+ const seen = /* @__PURE__ */ new Set();
1152
+ const walk = async (node) => {
1153
+ let cursor = null;
1154
+ do {
1155
+ const result = await aggregator.listChildren(scope, node.ref, {
1156
+ cursor
1157
+ });
1158
+ for (const entry of result.entries) {
1159
+ const key = nodeRefKey(entry.ref);
1160
+ if (seen.has(key)) {
1161
+ continue;
1162
+ }
1163
+ seen.add(key);
1164
+ if (entry.kind === "folder") {
1165
+ await walk(entry);
1166
+ } else {
1167
+ files.push(entry);
1168
+ }
1169
+ }
1170
+ cursor = result.nextCursor ?? null;
1171
+ } while (cursor);
1172
+ };
1173
+ await walk(folder);
1174
+ return files;
1175
+ };
1176
+ const scheduleSearch = (sourceId, query, filters, scopeNodeId) => {
1177
+ clearSearchTimer();
1178
+ if (!retained || !query && filters.length === 0) {
1179
+ return;
1180
+ }
1181
+ if (searchDebounceMs <= 0) {
1182
+ void runSearch(
1183
+ sourceId,
1184
+ query,
1185
+ filters,
1186
+ scopeNodeId,
1187
+ SEARCH_PAGE_SIZE,
1188
+ false
1189
+ );
1190
+ return;
1191
+ }
1192
+ searchTimer = setTimeout(() => {
1193
+ searchTimer = null;
1194
+ void runSearch(
1195
+ sourceId,
1196
+ query,
1197
+ filters,
1198
+ scopeNodeId,
1199
+ SEARCH_PAGE_SIZE,
1200
+ false
1201
+ );
1202
+ }, searchDebounceMs);
1203
+ };
1204
+ return {
1205
+ get store() {
1206
+ return store;
1207
+ },
1208
+ getSnapshot() {
1209
+ return snapshot;
1210
+ },
1211
+ open() {
1212
+ if (retained) {
1213
+ return;
1214
+ }
1215
+ retained = true;
1216
+ tabsReady = loadTabs();
1217
+ },
1218
+ close() {
1219
+ retained = false;
1220
+ cancelSearch();
1221
+ latestBrowseSeqByKey.clear();
1222
+ tabsSequence += 1;
1223
+ },
1224
+ reset() {
1225
+ cancelSearch();
1226
+ latestBrowseSeqByKey.clear();
1227
+ tabsSequence += 1;
1228
+ setSnapshot({
1229
+ isLoadingTabs: false,
1230
+ tabsError: null,
1231
+ tabs: [],
1232
+ activeSourceId: null,
1233
+ bySource: {},
1234
+ selection: []
1235
+ });
1236
+ },
1237
+ setActiveSource(sourceId) {
1238
+ if (!snapshot.tabs.some((tab) => tab.sourceId === sourceId)) {
1239
+ return;
1240
+ }
1241
+ const prevTab = snapshot.activeSourceId ? snapshot.bySource[snapshot.activeSourceId] : void 0;
1242
+ const carriedQuery = prevTab?.searchQuery ?? "";
1243
+ const carriedFilters = prevTab?.searchFilters ?? [];
1244
+ const trimmed = carriedQuery.trim();
1245
+ cancelSearch();
1246
+ setSnapshot({ activeSourceId: sourceId });
1247
+ if (trimmed === "" && carriedFilters.length === 0) {
1248
+ updateTab(
1249
+ sourceId,
1250
+ (tab) => tab.mode === "browse" && tab.searchQuery === "" && tab.searchFilters.length === 0 ? tab : {
1251
+ ...tab,
1252
+ mode: "browse",
1253
+ searchQuery: "",
1254
+ searchFilters: [],
1255
+ searchEntries: [],
1256
+ searchHasMore: false,
1257
+ isSearchLoading: false,
1258
+ isSearchLoadingMore: false,
1259
+ searchError: null
1260
+ }
1261
+ );
1262
+ ensureRootLoaded(sourceId);
1263
+ return;
1264
+ }
1265
+ const nextScopeNodeId = snapshot.bySource[sourceId]?.searchScopeNodeId ?? null;
1266
+ updateTab(sourceId, (tab) => ({
1267
+ ...tab,
1268
+ searchQuery: carriedQuery,
1269
+ searchFilters: carriedFilters,
1270
+ searchScopeNodeId: nextScopeNodeId,
1271
+ mode: "search",
1272
+ // 沿用旧结果直到新结果就绪,避免切源瞬间闪空。
1273
+ isSearchLoading: true,
1274
+ searchError: null
1275
+ }));
1276
+ scheduleSearch(sourceId, trimmed, carriedFilters, nextScopeNodeId);
1277
+ },
1278
+ async locatePath(target) {
1279
+ await tabsReady;
1280
+ if (!retained) {
1281
+ return [];
1282
+ }
1283
+ const refs = await aggregator.locateTarget(
1284
+ scope,
1285
+ target.sourceId,
1286
+ target.params
1287
+ );
1288
+ if (!refs || refs.length === 0) {
1289
+ return [];
1290
+ }
1291
+ const path = [];
1292
+ let parent = {
1293
+ sourceId: target.sourceId,
1294
+ nodeId: SOURCE_ROOT_NODE_ID
1295
+ };
1296
+ for (const ref of refs) {
1297
+ const { entries } = await aggregator.listChildren(scope, parent);
1298
+ const targetKey = nodeRefKey(ref);
1299
+ const node = entries.find(
1300
+ (entry) => nodeRefKey(entry.ref) === targetKey
1301
+ );
1302
+ if (!node) {
1303
+ break;
1304
+ }
1305
+ path.push(node);
1306
+ parent = ref;
1307
+ }
1308
+ return path;
1309
+ },
1310
+ ensureChildren(node) {
1311
+ const sourceId = node ? node.ref.sourceId : snapshot.activeSourceId;
1312
+ if (!sourceId) {
1313
+ return;
1314
+ }
1315
+ const key = childrenKeyForNode(node);
1316
+ const childState = snapshot.bySource[sourceId]?.childrenByKey[key];
1317
+ if (!childState?.loaded && !childState?.loading) {
1318
+ void loadChildren(sourceId, node, { append: false });
1319
+ }
1320
+ },
1321
+ ensureSourceRoot(sourceId) {
1322
+ if (!sourceId || !snapshot.tabs.some((tab) => tab.sourceId === sourceId)) {
1323
+ return;
1324
+ }
1325
+ ensureRootLoaded(sourceId);
1326
+ },
1327
+ toggleNode(node) {
1328
+ if (node.kind !== "folder") {
1329
+ return;
1330
+ }
1331
+ const sourceId = node.ref.sourceId;
1332
+ const key = nodeRefKey(node.ref);
1333
+ const wasExpanded = snapshot.bySource[sourceId]?.expandedKeys[key] ?? false;
1334
+ const nextExpanded = !wasExpanded;
1335
+ updateTab(sourceId, (tab) => ({
1336
+ ...tab,
1337
+ expandedKeys: { ...tab.expandedKeys, [key]: nextExpanded }
1338
+ }));
1339
+ const childState = snapshot.bySource[sourceId]?.childrenByKey[key];
1340
+ if (nextExpanded && !childState?.loaded && !childState?.loading) {
1341
+ void loadChildren(sourceId, node, { append: false });
1342
+ }
1343
+ },
1344
+ loadMore(node) {
1345
+ const sourceId = node ? node.ref.sourceId : snapshot.activeSourceId;
1346
+ if (!sourceId) {
1347
+ return;
1348
+ }
1349
+ void loadChildren(sourceId, node, { append: true });
1350
+ },
1351
+ loadMoreSourceRoot(sourceId) {
1352
+ if (!sourceId) {
1353
+ return;
1354
+ }
1355
+ void loadChildren(sourceId, null, { append: true });
1356
+ },
1357
+ setSearchQuery(query, scopeNodeId = null) {
1358
+ const sourceId = snapshot.activeSourceId;
1359
+ if (!sourceId) {
1360
+ return;
1361
+ }
1362
+ const filters = snapshot.bySource[sourceId]?.searchFilters ?? [];
1363
+ const trimmed = query.trim();
1364
+ const nextMode = trimmed || filters.length > 0 ? "search" : "browse";
1365
+ updateTab(sourceId, (tab) => ({
1366
+ ...tab,
1367
+ searchQuery: query,
1368
+ searchScopeNodeId: scopeNodeId,
1369
+ mode: nextMode,
1370
+ // 进入搜索:立刻置 loading(搜索是 debounce 的,否则键入到取数之间会先渲染空态,
1371
+ // 造成「空态 → spinner → 结果」闪烁)。保留上次 searchEntries,细化关键词时沿用旧结果而非闪空。
1372
+ ...nextMode === "browse" ? { isSearchLoading: false, searchEntries: [], searchError: null } : { isSearchLoading: true, searchError: null }
1373
+ }));
1374
+ if (nextMode === "search") {
1375
+ scheduleSearch(sourceId, trimmed, filters, scopeNodeId);
1376
+ } else {
1377
+ cancelSearch();
1378
+ ensureRootLoaded(sourceId);
1379
+ }
1380
+ },
1381
+ setSearchFilters(filters, scopeNodeId = null) {
1382
+ const sourceId = snapshot.activeSourceId;
1383
+ if (!sourceId) {
1384
+ return;
1385
+ }
1386
+ const tab = snapshot.bySource[sourceId];
1387
+ const trimmed = tab?.searchQuery.trim() ?? "";
1388
+ const scopeId = scopeNodeId ?? tab?.searchScopeNodeId ?? null;
1389
+ const nextMode = trimmed || filters.length > 0 ? "search" : "browse";
1390
+ updateTab(sourceId, (current) => ({
1391
+ ...current,
1392
+ searchFilters: filters,
1393
+ searchScopeNodeId: scopeId,
1394
+ mode: nextMode,
1395
+ ...nextMode === "browse" ? { isSearchLoading: false, searchEntries: [], searchError: null } : { isSearchLoading: true, searchError: null }
1396
+ }));
1397
+ if (nextMode === "search") {
1398
+ scheduleSearch(sourceId, trimmed, filters, scopeId);
1399
+ } else {
1400
+ cancelSearch();
1401
+ ensureRootLoaded(sourceId);
1402
+ }
1403
+ },
1404
+ setSearchScope(scopeNodeId) {
1405
+ const sourceId = snapshot.activeSourceId;
1406
+ if (!sourceId) {
1407
+ return;
1408
+ }
1409
+ const tab = snapshot.bySource[sourceId];
1410
+ if (!tab || tab.searchScopeNodeId === scopeNodeId) {
1411
+ return;
1412
+ }
1413
+ updateTab(sourceId, (current) => ({
1414
+ ...current,
1415
+ searchScopeNodeId: scopeNodeId
1416
+ }));
1417
+ const trimmed = tab.searchQuery.trim();
1418
+ const filters = tab.searchFilters;
1419
+ if (tab.mode === "search" && (trimmed || filters.length > 0)) {
1420
+ scheduleSearch(sourceId, trimmed, filters, scopeNodeId);
1421
+ }
1422
+ },
1423
+ loadMoreSearch() {
1424
+ const sourceId = snapshot.activeSourceId;
1425
+ if (!sourceId) {
1426
+ return;
1427
+ }
1428
+ const tab = snapshot.bySource[sourceId];
1429
+ if (!tab || tab.mode !== "search" || !tab.searchHasMore || tab.isSearchLoadingMore) {
1430
+ return;
1431
+ }
1432
+ const trimmed = tab.searchQuery.trim();
1433
+ if (!trimmed && tab.searchFilters.length === 0) {
1434
+ return;
1435
+ }
1436
+ const nextLimit = Math.min(
1437
+ (tab.searchLimit || SEARCH_PAGE_SIZE) + SEARCH_PAGE_SIZE,
1438
+ SEARCH_MAX_LIMIT
1439
+ );
1440
+ if (nextLimit <= tab.searchLimit) {
1441
+ return;
1442
+ }
1443
+ clearSearchTimer();
1444
+ void runSearch(
1445
+ sourceId,
1446
+ trimmed,
1447
+ tab.searchFilters,
1448
+ tab.searchScopeNodeId,
1449
+ nextLimit,
1450
+ true
1451
+ );
1452
+ },
1453
+ toggleSelection(node) {
1454
+ const key = nodeRefKey(node.ref);
1455
+ setSnapshot((current) => {
1456
+ const exists = current.selection.some(
1457
+ (item) => nodeRefKey(item.ref) === key
1458
+ );
1459
+ return {
1460
+ ...current,
1461
+ selection: exists ? current.selection.filter((item) => nodeRefKey(item.ref) !== key) : [...current.selection, node]
1462
+ };
1463
+ });
1464
+ },
1465
+ clearSelection() {
1466
+ setSnapshot({ selection: [] });
1467
+ },
1468
+ async confirm() {
1469
+ const resolved = [];
1470
+ const seenPaths = /* @__PURE__ */ new Set();
1471
+ const push = (ref) => {
1472
+ if (seenPaths.has(ref.path)) {
1473
+ return;
1474
+ }
1475
+ seenPaths.add(ref.path);
1476
+ resolved.push(ref);
1477
+ };
1478
+ for (const node of snapshot.selection) {
1479
+ if (node.kind !== "folder") {
1480
+ push(aggregator.resolveSelection(node));
1481
+ continue;
1482
+ }
1483
+ const navigable = aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.navigable ?? false;
1484
+ if (!navigable) {
1485
+ push(aggregator.resolveSelection(node));
1486
+ continue;
1487
+ }
1488
+ const files = await collectFolderFiles(node);
1489
+ for (const fileNode of files) {
1490
+ push(aggregator.resolveSelection(fileNode));
1491
+ }
1492
+ }
1493
+ return resolved;
1494
+ },
1495
+ async confirmGrouped() {
1496
+ const files = [];
1497
+ const bundles = [];
1498
+ const seenPaths = /* @__PURE__ */ new Set();
1499
+ const pushFile = (ref) => {
1500
+ if (seenPaths.has(ref.path)) {
1501
+ return;
1502
+ }
1503
+ seenPaths.add(ref.path);
1504
+ files.push(ref);
1505
+ };
1506
+ for (const node of snapshot.selection) {
1507
+ const navigable = node.kind === "folder" && (aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.navigable ?? false);
1508
+ if (!navigable) {
1509
+ pushFile(aggregator.resolveSelection(node));
1510
+ continue;
1511
+ }
1512
+ const fileNodes = await collectFolderFiles(node);
1513
+ const bundleSeen = /* @__PURE__ */ new Set();
1514
+ const bundleFiles = [];
1515
+ for (const fileNode of fileNodes) {
1516
+ const ref = aggregator.resolveSelection(fileNode);
1517
+ if (bundleSeen.has(ref.path)) {
1518
+ continue;
1519
+ }
1520
+ bundleSeen.add(ref.path);
1521
+ bundleFiles.push(ref);
1522
+ }
1523
+ bundles.push({ root: node, files: bundleFiles });
1524
+ }
1525
+ return { files, bundles };
1526
+ }
1527
+ };
1528
+ }
1529
+ function isAbortError2(error) {
1530
+ return error instanceof Error && error.name === "AbortError";
1531
+ }
1532
+ function normalizeError(error, fallback) {
1533
+ return error instanceof Error ? error : new Error(fallback);
1534
+ }
1535
+
891
1536
  export {
892
1537
  workspaceFileReferenceDefaultExpandedDepth,
893
1538
  normalizeDirectoryPath,
@@ -896,6 +1541,8 @@ export {
896
1541
  mergePrefetchedDirectoryState,
897
1542
  createWorkspaceFileReferenceDirectoryStateFromSnapshot,
898
1543
  prefetchReferenceTree,
899
- useWorkspaceFileReferencePickerView
1544
+ useWorkspaceFileReferencePickerView,
1545
+ ROOT_CHILDREN_KEY,
1546
+ createReferenceSourcePickerController
900
1547
  };
901
- //# sourceMappingURL=chunk-LS7P3J6Z.js.map
1548
+ //# sourceMappingURL=chunk-KSK6PILG.js.map