momoi-explorer 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs ADDED
@@ -0,0 +1,850 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var react_exports = {};
22
+ __export(react_exports, {
23
+ TreeContext: () => TreeContext,
24
+ TreeProvider: () => TreeProvider,
25
+ useContextMenu: () => useContextMenu,
26
+ useExplorerFocus: () => useExplorerFocus,
27
+ useExplorerKeybindings: () => useExplorerKeybindings,
28
+ useFileTree: () => useFileTree,
29
+ useTreeContext: () => useTreeContext,
30
+ useTreeNode: () => useTreeNode
31
+ });
32
+ module.exports = __toCommonJS(react_exports);
33
+
34
+ // src/react/TreeProvider.tsx
35
+ var import_react2 = require("react");
36
+
37
+ // src/core/event-processor.ts
38
+ var DEFAULT_DEBOUNCE_MS = 75;
39
+ var DEFAULT_THROTTLE_CHUNK_SIZE = 500;
40
+ var DEFAULT_THROTTLE_DELAY_MS = 200;
41
+ function coalesceEvents(raw) {
42
+ const expanded = [];
43
+ for (const event of raw) {
44
+ if (event.type === "rename" && event.newPath) {
45
+ expanded.push({ type: "delete", path: event.path, isDirectory: event.isDirectory });
46
+ expanded.push({ type: "create", path: event.newPath, isDirectory: event.isDirectory });
47
+ } else {
48
+ expanded.push({ type: event.type, path: event.path, isDirectory: event.isDirectory });
49
+ }
50
+ }
51
+ const byPath = /* @__PURE__ */ new Map();
52
+ for (const event of expanded) {
53
+ const existing = byPath.get(event.path);
54
+ if (!existing) {
55
+ byPath.set(event.path, event);
56
+ continue;
57
+ }
58
+ if (existing.type === "delete" && event.type === "create") {
59
+ byPath.set(event.path, { type: "modify", path: event.path, isDirectory: event.isDirectory });
60
+ continue;
61
+ }
62
+ if (existing.type === "create" && event.type === "modify") {
63
+ continue;
64
+ }
65
+ if (existing.type === "create" && event.type === "delete") {
66
+ byPath.delete(event.path);
67
+ continue;
68
+ }
69
+ byPath.set(event.path, event);
70
+ }
71
+ const result = Array.from(byPath.values());
72
+ const deletedDirs = /* @__PURE__ */ new Set();
73
+ for (const event of result) {
74
+ if (event.type === "delete" && event.isDirectory) {
75
+ deletedDirs.add(event.path);
76
+ }
77
+ }
78
+ if (deletedDirs.size === 0) return result;
79
+ return result.filter((event) => {
80
+ if (event.type !== "delete") return true;
81
+ for (const dir of deletedDirs) {
82
+ if (event.path !== dir && event.path.startsWith(dir + "/")) {
83
+ return false;
84
+ }
85
+ }
86
+ return true;
87
+ });
88
+ }
89
+ function createEventProcessor(callback, options = {}) {
90
+ const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
91
+ const shouldCoalesce = options.coalesce ?? true;
92
+ const throttleChunkSize = options.throttle?.maxChunkSize ?? DEFAULT_THROTTLE_CHUNK_SIZE;
93
+ const throttleDelayMs = options.throttle?.delayMs ?? DEFAULT_THROTTLE_DELAY_MS;
94
+ let buffer = [];
95
+ let debounceTimer = null;
96
+ let throttleTimer = null;
97
+ let destroyed = false;
98
+ function processBuffer() {
99
+ if (destroyed || buffer.length === 0) return;
100
+ const raw = buffer;
101
+ buffer = [];
102
+ const events = shouldCoalesce ? coalesceEvents(raw) : raw.map((e) => ({
103
+ type: e.type === "rename" ? "modify" : e.type,
104
+ path: e.type === "rename" && e.newPath ? e.newPath : e.path,
105
+ isDirectory: e.isDirectory
106
+ }));
107
+ if (events.length === 0) return;
108
+ if (events.length <= throttleChunkSize) {
109
+ callback(events);
110
+ return;
111
+ }
112
+ let offset = 0;
113
+ function emitChunk() {
114
+ if (destroyed || offset >= events.length) return;
115
+ const chunk = events.slice(offset, offset + throttleChunkSize);
116
+ offset += throttleChunkSize;
117
+ callback(chunk);
118
+ if (offset < events.length) {
119
+ throttleTimer = setTimeout(emitChunk, throttleDelayMs);
120
+ }
121
+ }
122
+ emitChunk();
123
+ }
124
+ return {
125
+ push(events) {
126
+ if (destroyed) return;
127
+ buffer.push(...events);
128
+ if (debounceTimer !== null) {
129
+ clearTimeout(debounceTimer);
130
+ }
131
+ debounceTimer = setTimeout(processBuffer, debounceMs);
132
+ },
133
+ flush() {
134
+ if (debounceTimer !== null) {
135
+ clearTimeout(debounceTimer);
136
+ debounceTimer = null;
137
+ }
138
+ processBuffer();
139
+ },
140
+ destroy() {
141
+ destroyed = true;
142
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
143
+ if (throttleTimer !== null) clearTimeout(throttleTimer);
144
+ buffer = [];
145
+ }
146
+ };
147
+ }
148
+
149
+ // src/core/flatten.ts
150
+ function flattenTree(nodes, expandedPaths, matchingPaths) {
151
+ const result = [];
152
+ function walk(children, depth) {
153
+ for (const node of children) {
154
+ if (matchingPaths && !matchingPaths.has(node.path)) continue;
155
+ result.push({ node, depth });
156
+ if (node.isDirectory && node.children) {
157
+ if (matchingPaths || expandedPaths.has(node.path)) {
158
+ walk(node.children, depth + 1);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ walk(nodes, 0);
164
+ return result;
165
+ }
166
+
167
+ // src/core/selection.ts
168
+ function computeSelection(currentSelected, anchorPath, targetPath, mode, flatList) {
169
+ switch (mode) {
170
+ case "replace":
171
+ return {
172
+ selectedPaths: /* @__PURE__ */ new Set([targetPath]),
173
+ anchorPath: targetPath
174
+ };
175
+ case "toggle": {
176
+ const next = new Set(currentSelected);
177
+ if (next.has(targetPath)) {
178
+ next.delete(targetPath);
179
+ } else {
180
+ next.add(targetPath);
181
+ }
182
+ return {
183
+ selectedPaths: next,
184
+ anchorPath: targetPath
185
+ };
186
+ }
187
+ case "range": {
188
+ if (!anchorPath) {
189
+ return {
190
+ selectedPaths: /* @__PURE__ */ new Set([targetPath]),
191
+ anchorPath: targetPath
192
+ };
193
+ }
194
+ const paths = flatList.map((f) => f.node.path);
195
+ const anchorIdx = paths.indexOf(anchorPath);
196
+ const targetIdx = paths.indexOf(targetPath);
197
+ if (anchorIdx === -1 || targetIdx === -1) {
198
+ return {
199
+ selectedPaths: /* @__PURE__ */ new Set([targetPath]),
200
+ anchorPath: targetPath
201
+ };
202
+ }
203
+ const start = Math.min(anchorIdx, targetIdx);
204
+ const end = Math.max(anchorIdx, targetIdx);
205
+ const rangePaths = new Set(paths.slice(start, end + 1));
206
+ return {
207
+ selectedPaths: rangePaths,
208
+ anchorPath
209
+ // range選択ではanchorは変えない
210
+ };
211
+ }
212
+ }
213
+ }
214
+
215
+ // src/core/sort.ts
216
+ function defaultSort(a, b) {
217
+ if (a.isDirectory !== b.isDirectory) {
218
+ return a.isDirectory ? -1 : 1;
219
+ }
220
+ return a.name.localeCompare(b.name, void 0, { sensitivity: "base" });
221
+ }
222
+
223
+ // src/core/filter.ts
224
+ function defaultFilter(_entry) {
225
+ return true;
226
+ }
227
+
228
+ // src/core/search.ts
229
+ function fuzzyMatch(query, target) {
230
+ const q = query.toLowerCase();
231
+ const t = target.toLowerCase();
232
+ if (q.length === 0) return { match: true, score: 0 };
233
+ if (q.length > t.length) return { match: false, score: 0 };
234
+ if (t === q) return { match: true, score: 100 };
235
+ if (t.startsWith(q)) return { match: true, score: 90 };
236
+ if (t.includes(q)) return { match: true, score: 80 };
237
+ let qi = 0;
238
+ let score = 0;
239
+ let lastMatchIndex = -2;
240
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
241
+ if (t[ti] === q[qi]) {
242
+ qi++;
243
+ if (ti === lastMatchIndex + 1) {
244
+ score += 5;
245
+ }
246
+ if (ti === 0 || "/\\-_.".includes(t[ti - 1])) {
247
+ score += 10;
248
+ }
249
+ score += 1;
250
+ lastMatchIndex = ti;
251
+ }
252
+ }
253
+ if (qi < q.length) return { match: false, score: 0 };
254
+ return { match: true, score };
255
+ }
256
+ function findMatchingPaths(nodes, query) {
257
+ const matching = /* @__PURE__ */ new Set();
258
+ function walk(node, ancestors) {
259
+ const nameMatch = fuzzyMatch(query, node.name).match;
260
+ let childMatch = false;
261
+ if (node.children) {
262
+ for (const child of node.children) {
263
+ if (walk(child, [...ancestors, node.path])) {
264
+ childMatch = true;
265
+ }
266
+ }
267
+ }
268
+ if (nameMatch || childMatch) {
269
+ matching.add(node.path);
270
+ for (const a of ancestors) {
271
+ matching.add(a);
272
+ }
273
+ return true;
274
+ }
275
+ return false;
276
+ }
277
+ for (const node of nodes) {
278
+ walk(node, []);
279
+ }
280
+ return matching;
281
+ }
282
+
283
+ // src/core/tree.ts
284
+ function toTreeNode(entry, depth) {
285
+ return {
286
+ ...entry,
287
+ depth,
288
+ children: entry.isDirectory ? void 0 : void 0,
289
+ childrenLoaded: false
290
+ };
291
+ }
292
+ function findNode(nodes, path) {
293
+ for (const node of nodes) {
294
+ if (node.path === path) return node;
295
+ if (node.children) {
296
+ const found = findNode(node.children, path);
297
+ if (found) return found;
298
+ }
299
+ }
300
+ return void 0;
301
+ }
302
+ function dirname(path) {
303
+ const sep = path.includes("\\") ? "\\" : "/";
304
+ const idx = path.lastIndexOf(sep);
305
+ return idx === -1 ? "" : path.slice(0, idx);
306
+ }
307
+ function createFileTree(options) {
308
+ const { adapter, rootPath, onEvent } = options;
309
+ let sortFn = options.sort ?? defaultSort;
310
+ let filterFn = options.filter ?? defaultFilter;
311
+ let state = {
312
+ rootPath,
313
+ rootNodes: [],
314
+ expandedPaths: /* @__PURE__ */ new Set(),
315
+ selectedPaths: /* @__PURE__ */ new Set(),
316
+ anchorPath: null,
317
+ renamingPath: null,
318
+ creatingState: null,
319
+ searchQuery: null,
320
+ flatList: []
321
+ };
322
+ const listeners = /* @__PURE__ */ new Set();
323
+ let expandingPaths = /* @__PURE__ */ new Set();
324
+ function emit(event) {
325
+ onEvent?.(event);
326
+ }
327
+ function notify() {
328
+ const matchingPaths = state.searchQuery ? findMatchingPaths(state.rootNodes, state.searchQuery) : null;
329
+ state = { ...state, flatList: flattenTree(state.rootNodes, state.expandedPaths, matchingPaths) };
330
+ for (const listener of listeners) {
331
+ listener(state);
332
+ }
333
+ }
334
+ async function loadChildren(node) {
335
+ const entries = await adapter.readDir(node.path);
336
+ const filtered = entries.filter(filterFn);
337
+ filtered.sort(sortFn);
338
+ const oldChildMap = node.children ? new Map(node.children.map((c) => [c.path, c])) : /* @__PURE__ */ new Map();
339
+ node.children = filtered.map((e) => {
340
+ const existing = oldChildMap.get(e.path);
341
+ if (existing && existing.childrenLoaded) {
342
+ return { ...toTreeNode(e, node.depth + 1), children: existing.children, childrenLoaded: true };
343
+ }
344
+ return toTreeNode(e, node.depth + 1);
345
+ });
346
+ node.childrenLoaded = true;
347
+ }
348
+ async function refreshParent(parentPath) {
349
+ const parentNode = findNode(state.rootNodes, parentPath);
350
+ if (parentNode) {
351
+ await loadChildren(parentNode);
352
+ state.expandedPaths = new Set(state.expandedPaths);
353
+ state.expandedPaths.add(parentPath);
354
+ } else if (parentPath === rootPath) {
355
+ const entries = await adapter.readDir(rootPath);
356
+ const filtered = entries.filter(filterFn);
357
+ filtered.sort(sortFn);
358
+ const oldNodeMap = new Map(state.rootNodes.map((n) => [n.path, n]));
359
+ state.rootNodes = filtered.map((e) => {
360
+ const existing = oldNodeMap.get(e.path);
361
+ if (existing && existing.childrenLoaded) {
362
+ return { ...toTreeNode(e, 0), children: existing.children, childrenLoaded: true };
363
+ }
364
+ return toTreeNode(e, 0);
365
+ });
366
+ }
367
+ }
368
+ function sortNodes(nodes) {
369
+ nodes.sort(sortFn);
370
+ for (const node of nodes) {
371
+ if (node.children) {
372
+ sortNodes(node.children);
373
+ }
374
+ }
375
+ }
376
+ function filterNodes(nodes) {
377
+ return nodes.filter((node) => {
378
+ if (!filterFn(node)) return false;
379
+ if (node.children) {
380
+ node.children = filterNodes(node.children);
381
+ }
382
+ return true;
383
+ });
384
+ }
385
+ let unwatchFn = null;
386
+ let eventProcessor = null;
387
+ function handleWatchEvents(events) {
388
+ emit({ type: "external-change", changes: events });
389
+ const dirsToRefresh = /* @__PURE__ */ new Set();
390
+ for (const event of events) {
391
+ const parent = dirname(event.path);
392
+ if (parent === rootPath || state.expandedPaths.has(parent)) {
393
+ dirsToRefresh.add(parent);
394
+ }
395
+ if (event.isDirectory && state.expandedPaths.has(event.path)) {
396
+ dirsToRefresh.add(event.path);
397
+ }
398
+ }
399
+ for (const dir of dirsToRefresh) {
400
+ refreshParent(dir).then(() => notify()).catch(() => {
401
+ });
402
+ }
403
+ }
404
+ function startWatching() {
405
+ if (!adapter.watch) return;
406
+ eventProcessor = createEventProcessor(handleWatchEvents, options.watchOptions);
407
+ unwatchFn = adapter.watch(rootPath, (events) => {
408
+ eventProcessor.push(events);
409
+ });
410
+ }
411
+ function stopWatching() {
412
+ if (unwatchFn) {
413
+ unwatchFn();
414
+ unwatchFn = null;
415
+ }
416
+ if (eventProcessor) {
417
+ eventProcessor.destroy();
418
+ eventProcessor = null;
419
+ }
420
+ }
421
+ const controller = {
422
+ getState() {
423
+ return state;
424
+ },
425
+ subscribe(listener) {
426
+ listeners.add(listener);
427
+ return () => listeners.delete(listener);
428
+ },
429
+ async loadRoot() {
430
+ const entries = await adapter.readDir(rootPath);
431
+ const filtered = entries.filter(filterFn);
432
+ filtered.sort(sortFn);
433
+ state.rootNodes = filtered.map((e) => toTreeNode(e, 0));
434
+ notify();
435
+ startWatching();
436
+ },
437
+ async expand(path) {
438
+ if (expandingPaths.has(path)) return;
439
+ expandingPaths.add(path);
440
+ try {
441
+ const node = findNode(state.rootNodes, path);
442
+ if (!node || !node.isDirectory) return;
443
+ if (!node.childrenLoaded) {
444
+ await loadChildren(node);
445
+ }
446
+ state.expandedPaths = new Set(state.expandedPaths);
447
+ state.expandedPaths.add(path);
448
+ notify();
449
+ emit({ type: "expand", path });
450
+ } finally {
451
+ expandingPaths.delete(path);
452
+ }
453
+ },
454
+ collapse(path) {
455
+ const sep = path.includes("\\") ? "\\" : "/";
456
+ const prefix = path + sep;
457
+ state.expandedPaths = new Set(state.expandedPaths);
458
+ state.expandedPaths.delete(path);
459
+ for (const p of state.expandedPaths) {
460
+ if (p.startsWith(prefix)) {
461
+ state.expandedPaths.delete(p);
462
+ }
463
+ }
464
+ notify();
465
+ emit({ type: "collapse", path });
466
+ },
467
+ async toggleExpand(path) {
468
+ if (expandingPaths.has(path)) return;
469
+ if (state.expandedPaths.has(path)) {
470
+ controller.collapse(path);
471
+ } else {
472
+ await controller.expand(path);
473
+ }
474
+ },
475
+ async expandTo(path) {
476
+ const parts = [];
477
+ let current = path;
478
+ while (current !== rootPath && current !== "") {
479
+ const parent = dirname(current);
480
+ if (parent === current) break;
481
+ parts.unshift(parent);
482
+ current = parent;
483
+ }
484
+ for (const ancestorPath of parts) {
485
+ if (ancestorPath === rootPath) continue;
486
+ if (!state.expandedPaths.has(ancestorPath)) {
487
+ await controller.expand(ancestorPath);
488
+ }
489
+ }
490
+ },
491
+ select(path, mode = "replace") {
492
+ const result = computeSelection(
493
+ state.selectedPaths,
494
+ state.anchorPath,
495
+ path,
496
+ mode,
497
+ state.flatList
498
+ );
499
+ state.selectedPaths = result.selectedPaths;
500
+ state.anchorPath = result.anchorPath;
501
+ notify();
502
+ emit({ type: "select", paths: Array.from(result.selectedPaths) });
503
+ },
504
+ selectAll() {
505
+ state.selectedPaths = new Set(state.flatList.map((f) => f.node.path));
506
+ notify();
507
+ emit({ type: "select", paths: Array.from(state.selectedPaths) });
508
+ },
509
+ clearSelection() {
510
+ state.selectedPaths = /* @__PURE__ */ new Set();
511
+ state.anchorPath = null;
512
+ notify();
513
+ emit({ type: "select", paths: [] });
514
+ },
515
+ startRename(path) {
516
+ state.renamingPath = path;
517
+ notify();
518
+ },
519
+ async commitRename(newName) {
520
+ if (!state.renamingPath || !adapter.rename) return;
521
+ const oldPath = state.renamingPath;
522
+ const parent = dirname(oldPath);
523
+ const sep = oldPath.includes("\\") ? "\\" : "/";
524
+ const newPath = parent + sep + newName;
525
+ await adapter.rename(oldPath, newPath);
526
+ state.renamingPath = null;
527
+ if (parent === rootPath) {
528
+ await controller.loadRoot();
529
+ } else {
530
+ const parentNode = findNode(state.rootNodes, parent);
531
+ if (parentNode) {
532
+ await loadChildren(parentNode);
533
+ }
534
+ }
535
+ notify();
536
+ emit({ type: "rename", oldPath, newPath });
537
+ },
538
+ cancelRename() {
539
+ state.renamingPath = null;
540
+ notify();
541
+ },
542
+ async startCreate(parentPath, isDirectory, insertAfterPath) {
543
+ if (parentPath !== rootPath && !state.expandedPaths.has(parentPath)) {
544
+ await controller.expand(parentPath);
545
+ }
546
+ state.creatingState = { parentPath, isDirectory, insertAfterPath };
547
+ notify();
548
+ },
549
+ async commitCreate(name) {
550
+ if (!state.creatingState) return;
551
+ const { parentPath, isDirectory } = state.creatingState;
552
+ state.creatingState = null;
553
+ if (isDirectory) {
554
+ await controller.createDir(parentPath, name);
555
+ } else {
556
+ await controller.createFile(parentPath, name);
557
+ }
558
+ },
559
+ cancelCreate() {
560
+ state.creatingState = null;
561
+ notify();
562
+ },
563
+ async createFile(parentPath, name) {
564
+ if (!adapter.createFile) return;
565
+ await adapter.createFile(parentPath, name);
566
+ await refreshParent(parentPath);
567
+ notify();
568
+ emit({ type: "create", parentPath, name, isDirectory: false });
569
+ },
570
+ async createDir(parentPath, name) {
571
+ if (!adapter.createDir) return;
572
+ await adapter.createDir(parentPath, name);
573
+ await refreshParent(parentPath);
574
+ notify();
575
+ emit({ type: "create", parentPath, name, isDirectory: true });
576
+ },
577
+ async deleteSelected() {
578
+ if (!adapter.delete || state.selectedPaths.size === 0) return;
579
+ const paths = Array.from(state.selectedPaths);
580
+ await adapter.delete(paths);
581
+ state.selectedPaths = /* @__PURE__ */ new Set();
582
+ state.anchorPath = null;
583
+ const parentDirs = new Set(paths.map(dirname));
584
+ for (const dir of parentDirs) {
585
+ await refreshParent(dir);
586
+ }
587
+ notify();
588
+ emit({ type: "delete", paths });
589
+ },
590
+ async refresh(path) {
591
+ if (!path || path === rootPath) {
592
+ await refreshParent(rootPath);
593
+ } else {
594
+ const node = findNode(state.rootNodes, path);
595
+ if (node && node.isDirectory) {
596
+ await loadChildren(node);
597
+ }
598
+ }
599
+ notify();
600
+ emit({ type: "refresh", path });
601
+ },
602
+ setSearchQuery(query) {
603
+ state.searchQuery = query && query.trim() ? query.trim() : null;
604
+ notify();
605
+ },
606
+ async collectAllFiles() {
607
+ const result = [];
608
+ async function walk(dirPath) {
609
+ const entries = await adapter.readDir(dirPath);
610
+ for (const entry of entries) {
611
+ if (!filterFn(entry)) continue;
612
+ result.push(entry);
613
+ if (entry.isDirectory) {
614
+ await walk(entry.path);
615
+ }
616
+ }
617
+ }
618
+ await walk(rootPath);
619
+ return result;
620
+ },
621
+ setFilter(fn) {
622
+ filterFn = fn ?? defaultFilter;
623
+ state.rootNodes = filterNodes(state.rootNodes);
624
+ notify();
625
+ },
626
+ setSort(fn) {
627
+ sortFn = fn ?? defaultSort;
628
+ sortNodes(state.rootNodes);
629
+ notify();
630
+ },
631
+ destroy() {
632
+ stopWatching();
633
+ listeners.clear();
634
+ }
635
+ };
636
+ return controller;
637
+ }
638
+
639
+ // src/react/context.ts
640
+ var import_react = require("react");
641
+ var TreeContext = (0, import_react.createContext)(null);
642
+ function useTreeContext() {
643
+ const ctx = (0, import_react.useContext)(TreeContext);
644
+ if (!ctx) {
645
+ throw new Error("useTreeContext must be used within a <TreeProvider>");
646
+ }
647
+ return ctx;
648
+ }
649
+
650
+ // src/react/TreeProvider.tsx
651
+ var import_jsx_runtime = require("react/jsx-runtime");
652
+ function TreeProvider({
653
+ adapter,
654
+ rootPath,
655
+ sort,
656
+ filter,
657
+ watchOptions,
658
+ onEvent,
659
+ children
660
+ }) {
661
+ const onEventRef = (0, import_react2.useRef)(onEvent);
662
+ onEventRef.current = onEvent;
663
+ const controller = (0, import_react2.useMemo)(() => {
664
+ return createFileTree({
665
+ adapter,
666
+ rootPath,
667
+ sort,
668
+ filter,
669
+ watchOptions,
670
+ onEvent: (event) => onEventRef.current?.(event)
671
+ });
672
+ }, [adapter, rootPath]);
673
+ const [state, setState] = (0, import_react2.useState)(() => controller.getState());
674
+ (0, import_react2.useEffect)(() => {
675
+ const unsub = controller.subscribe(setState);
676
+ controller.loadRoot();
677
+ return () => {
678
+ unsub();
679
+ controller.destroy();
680
+ };
681
+ }, [controller]);
682
+ const value = (0, import_react2.useMemo)(() => ({ controller, state }), [controller, state]);
683
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TreeContext.Provider, { value, children });
684
+ }
685
+
686
+ // src/react/useFileTree.ts
687
+ function useFileTree() {
688
+ const { controller, state } = useTreeContext();
689
+ return { ...state, controller };
690
+ }
691
+
692
+ // src/react/useTreeNode.ts
693
+ var import_react3 = require("react");
694
+ function useTreeNode(path) {
695
+ const { state } = useTreeContext();
696
+ return (0, import_react3.useMemo)(() => {
697
+ const flatItem = state.flatList.find((f) => f.node.path === path);
698
+ if (!flatItem) return null;
699
+ return {
700
+ node: flatItem.node,
701
+ isExpanded: state.expandedPaths.has(path),
702
+ isSelected: state.selectedPaths.has(path),
703
+ isRenaming: state.renamingPath === path,
704
+ depth: flatItem.depth
705
+ };
706
+ }, [state, path]);
707
+ }
708
+
709
+ // src/react/useContextMenu.ts
710
+ var import_react4 = require("react");
711
+ function useContextMenu() {
712
+ const [menuState, setMenuState] = (0, import_react4.useState)({
713
+ isVisible: false,
714
+ x: 0,
715
+ y: 0,
716
+ targetPath: null
717
+ });
718
+ const show = (0, import_react4.useCallback)((e, targetPath) => {
719
+ e.preventDefault();
720
+ e.stopPropagation();
721
+ setMenuState({
722
+ isVisible: true,
723
+ x: e.clientX,
724
+ y: e.clientY,
725
+ targetPath
726
+ });
727
+ }, []);
728
+ const hide = (0, import_react4.useCallback)(() => {
729
+ setMenuState((prev) => ({ ...prev, isVisible: false, targetPath: null }));
730
+ }, []);
731
+ return { ...menuState, show, hide };
732
+ }
733
+
734
+ // src/react/useExplorerKeybindings.ts
735
+ var import_react5 = require("react");
736
+
737
+ // src/core/keybindings.ts
738
+ var ExplorerCommands = {
739
+ DELETE: "explorer.delete",
740
+ RENAME: "explorer.rename",
741
+ NEW_FILE: "explorer.newFile",
742
+ NEW_FOLDER: "explorer.newFolder",
743
+ REFRESH: "explorer.refresh",
744
+ COLLAPSE_ALL: "explorer.collapseAll",
745
+ SELECT_ALL: "explorer.selectAll",
746
+ COPY_PATH: "explorer.copyPath"
747
+ };
748
+ var defaultExplorerKeybindings = [
749
+ { key: "Delete", command: ExplorerCommands.DELETE, when: "explorerFocus" },
750
+ { key: "F2", command: ExplorerCommands.RENAME, when: "explorerFocus" },
751
+ { key: "Ctrl+N", command: ExplorerCommands.NEW_FILE, when: "explorerFocus" },
752
+ { key: "Ctrl+Shift+N", command: ExplorerCommands.NEW_FOLDER, when: "explorerFocus" },
753
+ { key: "Ctrl+R", command: ExplorerCommands.REFRESH, when: "explorerFocus" },
754
+ { key: "Ctrl+Shift+E", command: ExplorerCommands.COLLAPSE_ALL, when: "explorerFocus" },
755
+ { key: "Ctrl+A", command: ExplorerCommands.SELECT_ALL, when: "explorerFocus" },
756
+ { key: "Ctrl+Shift+C", command: ExplorerCommands.COPY_PATH, when: "explorerFocus" }
757
+ ];
758
+
759
+ // src/react/useExplorerKeybindings.ts
760
+ function useExplorerKeybindings(inputService, options) {
761
+ const { controller, state } = useTreeContext();
762
+ const stateRef = (0, import_react5.useRef)(state);
763
+ stateRef.current = state;
764
+ const optionsRef = (0, import_react5.useRef)(options);
765
+ optionsRef.current = options;
766
+ const getCreateTarget = (0, import_react5.useCallback)(() => {
767
+ const s = stateRef.current;
768
+ if (s.selectedPaths.size === 0) return { parentPath: s.rootPath };
769
+ const firstSelected = s.flatList.find((f) => s.selectedPaths.has(f.node.path));
770
+ if (!firstSelected) return { parentPath: s.rootPath };
771
+ if (firstSelected.node.isDirectory) return { parentPath: firstSelected.node.path };
772
+ const sep = firstSelected.node.path.includes("\\") ? "\\" : "/";
773
+ const idx = firstSelected.node.path.lastIndexOf(sep);
774
+ const parentPath = idx === -1 ? s.rootPath : firstSelected.node.path.slice(0, idx);
775
+ return { parentPath, insertAfterPath: firstSelected.node.path };
776
+ }, []);
777
+ (0, import_react5.useEffect)(() => {
778
+ if (!inputService) return;
779
+ const disposers = [];
780
+ const handlers = {
781
+ [ExplorerCommands.DELETE]: () => controller.deleteSelected(),
782
+ [ExplorerCommands.RENAME]: () => {
783
+ const s = stateRef.current;
784
+ if (s.selectedPaths.size === 1) {
785
+ controller.startRename(Array.from(s.selectedPaths)[0]);
786
+ }
787
+ },
788
+ [ExplorerCommands.NEW_FILE]: () => {
789
+ const t = getCreateTarget();
790
+ controller.startCreate(t.parentPath, false, t.insertAfterPath);
791
+ },
792
+ [ExplorerCommands.NEW_FOLDER]: () => {
793
+ const t = getCreateTarget();
794
+ controller.startCreate(t.parentPath, true, t.insertAfterPath);
795
+ },
796
+ [ExplorerCommands.REFRESH]: () => controller.refresh(),
797
+ [ExplorerCommands.COLLAPSE_ALL]: () => {
798
+ const s = stateRef.current;
799
+ for (const path of s.expandedPaths) {
800
+ controller.collapse(path);
801
+ }
802
+ },
803
+ [ExplorerCommands.SELECT_ALL]: () => controller.selectAll(),
804
+ [ExplorerCommands.COPY_PATH]: () => {
805
+ const s = stateRef.current;
806
+ const paths = Array.from(s.selectedPaths);
807
+ optionsRef.current?.onCopyPath?.(paths);
808
+ }
809
+ };
810
+ for (const [command, handler] of Object.entries(handlers)) {
811
+ disposers.push(inputService.registerCommand(command, handler));
812
+ }
813
+ return () => {
814
+ for (const dispose of disposers) {
815
+ dispose();
816
+ }
817
+ };
818
+ }, [inputService, controller, getCreateTarget]);
819
+ }
820
+
821
+ // src/react/useExplorerFocus.ts
822
+ var import_react6 = require("react");
823
+ function useExplorerFocus(inputService, contextKey = "explorerFocus") {
824
+ const focused = (0, import_react6.useRef)(false);
825
+ const onFocus = (0, import_react6.useCallback)(() => {
826
+ if (!focused.current) {
827
+ focused.current = true;
828
+ inputService?.setContext(contextKey, true);
829
+ }
830
+ }, [inputService, contextKey]);
831
+ const onBlur = (0, import_react6.useCallback)(() => {
832
+ if (focused.current) {
833
+ focused.current = false;
834
+ inputService?.deleteContext(contextKey);
835
+ }
836
+ }, [inputService, contextKey]);
837
+ return { onFocus, onBlur, tabIndex: 0 };
838
+ }
839
+ // Annotate the CommonJS export names for ESM import in node:
840
+ 0 && (module.exports = {
841
+ TreeContext,
842
+ TreeProvider,
843
+ useContextMenu,
844
+ useExplorerFocus,
845
+ useExplorerKeybindings,
846
+ useFileTree,
847
+ useTreeContext,
848
+ useTreeNode
849
+ });
850
+ //# sourceMappingURL=react.cjs.map