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.
@@ -0,0 +1,499 @@
1
+ import * as React$1 from 'react';
2
+ import React__default, { ReactNode } from 'react';
3
+
4
+ /**
5
+ * ファイルシステムへのアクセスを抽象化するアダプタインターフェース。
6
+ * ユーザーはこのインターフェースを実装してcreateFileTreeに渡す。
7
+ * `readDir` のみ必須。その他のメソッドはオプションで、対応する機能が有効になる。
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { FileSystemAdapter } from 'momoi-explorer'
12
+ * import fs from 'node:fs/promises'
13
+ * import path from 'node:path'
14
+ *
15
+ * const adapter: FileSystemAdapter = {
16
+ * async readDir(dirPath) {
17
+ * const entries = await fs.readdir(dirPath, { withFileTypes: true })
18
+ * return entries.map(e => ({
19
+ * name: e.name,
20
+ * path: path.join(dirPath, e.name),
21
+ * isDirectory: e.isDirectory(),
22
+ * }))
23
+ * },
24
+ * async rename(oldPath, newPath) { await fs.rename(oldPath, newPath) },
25
+ * async delete(paths) { for (const p of paths) await fs.rm(p, { recursive: true }) },
26
+ * }
27
+ * ```
28
+ */
29
+ interface FileSystemAdapter {
30
+ /**
31
+ * ディレクトリの中身を読み取る。唯一の必須メソッド。
32
+ * @param path - 読み取るディレクトリの絶対パス
33
+ * @returns ディレクトリ内のFileEntryの配列
34
+ */
35
+ readDir(path: string): Promise<FileEntry[]>;
36
+ /**
37
+ * ファイルまたはフォルダをリネームする。
38
+ * 実装するとUI上でリネーム操作が可能になる。
39
+ * @param oldPath - リネーム前の絶対パス
40
+ * @param newPath - リネーム後の絶対パス
41
+ */
42
+ rename?(oldPath: string, newPath: string): Promise<void>;
43
+ /**
44
+ * ファイルまたはフォルダを削除する。
45
+ * 実装すると選択アイテムの削除操作が可能になる。
46
+ * @param paths - 削除対象の絶対パスの配列
47
+ */
48
+ delete?(paths: string[]): Promise<void>;
49
+ /**
50
+ * ファイルを新規作成する。
51
+ * 実装するとUI上でファイル作成操作が可能になる。
52
+ * @param parentPath - 作成先の親ディレクトリの絶対パス
53
+ * @param name - 新規ファイル名
54
+ */
55
+ createFile?(parentPath: string, name: string): Promise<void>;
56
+ /**
57
+ * フォルダを新規作成する。
58
+ * 実装するとUI上でフォルダ作成操作が可能になる。
59
+ * @param parentPath - 作成先の親ディレクトリの絶対パス
60
+ * @param name - 新規フォルダ名
61
+ */
62
+ createDir?(parentPath: string, name: string): Promise<void>;
63
+ /**
64
+ * ファイル変更監視を開始する。
65
+ * 生イベントを投げるだけでOK。デバウンス・合体・スロットリングはコアが行う。
66
+ * @param path - 監視するディレクトリの絶対パス
67
+ * @param callback - 生イベントを受け取るコールバック
68
+ * @returns 監視を停止するunwatch関数
69
+ */
70
+ watch?(path: string, callback: (events: RawWatchEvent[]) => void): () => void;
71
+ }
72
+ /**
73
+ * ファイルまたはフォルダを表すエントリ。
74
+ * `readDir` の戻り値として使用される基本データ型。
75
+ */
76
+ interface FileEntry {
77
+ /** ファイル名(拡張子含む) */
78
+ name: string;
79
+ /** 絶対パス */
80
+ path: string;
81
+ /** ディレクトリならtrue */
82
+ isDirectory: boolean;
83
+ /**
84
+ * ユーザー拡張用のメタデータ。
85
+ * gitStatus, iconHint 等、任意のデータを格納できる。
86
+ */
87
+ meta?: Record<string, unknown>;
88
+ }
89
+ /**
90
+ * コア内部で使用されるツリーノード。FileEntryにツリー構造の情報を追加したもの。
91
+ * flatList内のFlatNode.nodeとしても参照される。
92
+ */
93
+ interface TreeNode extends FileEntry {
94
+ /** ルートからの深さ(ルート直下 = 0) */
95
+ depth: number;
96
+ /** 子ノード。ディレクトリで未読み込みの場合はundefined */
97
+ children?: TreeNode[];
98
+ /** 子ノードが読み込み済みかどうか */
99
+ childrenLoaded: boolean;
100
+ }
101
+ /**
102
+ * 仮想スクロール用のフラットリスト要素。
103
+ * ツリーを展開状態に基づいて一次元配列に変換したもの。
104
+ */
105
+ interface FlatNode {
106
+ /** 対応するツリーノード */
107
+ node: TreeNode;
108
+ /** 表示上の深さ(インデント計算に使用) */
109
+ depth: number;
110
+ }
111
+ /** インライン新規作成の状態 */
112
+ interface CreatingState {
113
+ /** 新規アイテムを作成する親ディレクトリのパス */
114
+ parentPath: string;
115
+ /** ファイルかフォルダか */
116
+ isDirectory: boolean;
117
+ /** この位置の直後に入力行を表示する。未指定なら親の子の先頭(フォルダ選択時)or末尾(選択なし時) */
118
+ insertAfterPath?: string;
119
+ }
120
+ /**
121
+ * ツリー全体の現在状態。subscribeで購読可能。
122
+ * 不変(immutable)として扱い、変更時は新しいオブジェクトが生成される。
123
+ */
124
+ interface TreeState {
125
+ /** ルートディレクトリの絶対パス */
126
+ rootPath: string;
127
+ /** ルート直下のツリーノード配列 */
128
+ rootNodes: TreeNode[];
129
+ /** 現在展開中のディレクトリパスのセット */
130
+ expandedPaths: Set<string>;
131
+ /** 現在選択中のパスのセット */
132
+ selectedPaths: Set<string>;
133
+ /** 範囲選択の起点パス(Shift+Click用) */
134
+ anchorPath: string | null;
135
+ /** 現在リネーム中のパス。nullならリネームモードではない */
136
+ renamingPath: string | null;
137
+ /** インライン新規作成の状態。nullなら作成モードではない */
138
+ creatingState: CreatingState | null;
139
+ /** 現在の検索クエリ。nullなら検索なし */
140
+ searchQuery: string | null;
141
+ /** 仮想スクロール用のフラットリスト(展開・検索状態を反映) */
142
+ flatList: FlatNode[];
143
+ }
144
+ /**
145
+ * アダプタのwatchから来る生のファイル監視イベント。
146
+ * renameイベントはnewPathを持つ。コアが合体処理を行う。
147
+ */
148
+ interface RawWatchEvent {
149
+ /** イベント種別 */
150
+ type: 'create' | 'modify' | 'delete' | 'rename';
151
+ /** 対象パス(renameの場合はリネーム前のパス) */
152
+ path: string;
153
+ /** renameの場合のリネーム後パス */
154
+ newPath?: string;
155
+ /** 対象がディレクトリかどうか */
156
+ isDirectory: boolean;
157
+ }
158
+ /**
159
+ * コア内部で合体処理後のイベント。
160
+ * renameはdelete+createに分解され、同一パスのイベントは合体される。
161
+ */
162
+ interface WatchEvent {
163
+ /** イベント種別(renameは含まない) */
164
+ type: 'create' | 'modify' | 'delete';
165
+ /** 対象パス */
166
+ path: string;
167
+ /** 対象がディレクトリかどうか */
168
+ isDirectory: boolean;
169
+ }
170
+ /**
171
+ * ツリー操作や外部変更をUI層に通知するためのイベント型。
172
+ * `FileTreeOptions.onEvent` で購読できる。
173
+ *
174
+ * - `expand` / `collapse` - ディレクトリの展開/折りたたみ
175
+ * - `select` - 選択変更
176
+ * - `open` - ファイルを開く
177
+ * - `rename` - リネーム完了
178
+ * - `delete` - 削除完了
179
+ * - `create` - ファイル/フォルダ作成完了
180
+ * - `refresh` - ツリーのリフレッシュ
181
+ * - `external-change` - ファイル監視による外部変更検出
182
+ */
183
+ type TreeEvent = {
184
+ type: 'expand';
185
+ path: string;
186
+ } | {
187
+ type: 'collapse';
188
+ path: string;
189
+ } | {
190
+ type: 'select';
191
+ paths: string[];
192
+ } | {
193
+ type: 'open';
194
+ path: string;
195
+ } | {
196
+ type: 'rename';
197
+ oldPath: string;
198
+ newPath: string;
199
+ } | {
200
+ type: 'delete';
201
+ paths: string[];
202
+ } | {
203
+ type: 'create';
204
+ parentPath: string;
205
+ name: string;
206
+ isDirectory: boolean;
207
+ } | {
208
+ type: 'refresh';
209
+ path?: string;
210
+ } | {
211
+ type: 'external-change';
212
+ changes: WatchEvent[];
213
+ };
214
+ /**
215
+ * ファイル監視の動作設定。
216
+ * createFileTreeのoptionsで指定する。
217
+ */
218
+ interface WatchOptions {
219
+ /** デバウンス時間(ms)。デフォルト: 75 (VSCode準拠) */
220
+ debounceMs?: number;
221
+ /** イベント合体を行うか。デフォルト: true */
222
+ coalesce?: boolean;
223
+ /** スロットリング設定。大量イベント発生時にチャンク分割する */
224
+ throttle?: {
225
+ /** 一度に処理するイベント上限。デフォルト: 500 */
226
+ maxChunkSize: number;
227
+ /** チャンク間の休止時間(ms)。デフォルト: 200 */
228
+ delayMs: number;
229
+ };
230
+ }
231
+ /**
232
+ * {@link createFileTree} の初期化オプション。
233
+ */
234
+ interface FileTreeOptions {
235
+ /** ファイルシステムアダプタ(必須) */
236
+ adapter: FileSystemAdapter;
237
+ /** ツリーのルートディレクトリの絶対パス */
238
+ rootPath: string;
239
+ /** カスタムソート関数。未指定時はフォルダ優先・名前昇順 */
240
+ sort?: (a: FileEntry, b: FileEntry) => number;
241
+ /** カスタムフィルタ関数。falseを返すエントリは非表示になる */
242
+ filter?: (entry: FileEntry) => boolean;
243
+ /** ファイル監視のオプション */
244
+ watchOptions?: WatchOptions;
245
+ /** ツリー操作イベントのコールバック */
246
+ onEvent?: (event: TreeEvent) => void;
247
+ }
248
+ /**
249
+ * ヘッドレスファイルツリーのメインコントローラ。
250
+ * {@link createFileTree} から返される。状態購読・ツリー操作・編集・検索などの全APIを提供する。
251
+ */
252
+ interface FileTreeController {
253
+ /**
254
+ * 現在のツリー状態を取得する。
255
+ * @returns 現在のTreeState
256
+ */
257
+ getState(): TreeState;
258
+ /**
259
+ * 状態変更を購読する。状態が変わるたびにlistenerが呼ばれる。
260
+ * @param listener - 状態変更時に呼ばれるコールバック
261
+ * @returns 購読解除関数
262
+ */
263
+ subscribe(listener: (state: TreeState) => void): () => void;
264
+ /**
265
+ * ルートディレクトリを読み込み、ツリーを初期化する。
266
+ * ファイル監視もここで開始される。最初に必ず呼ぶこと。
267
+ */
268
+ loadRoot(): Promise<void>;
269
+ /**
270
+ * 指定パスのディレクトリを展開する。未読み込みなら子を読み込む。
271
+ * @param path - 展開するディレクトリの絶対パス
272
+ */
273
+ expand(path: string): Promise<void>;
274
+ /**
275
+ * 指定パスのディレクトリを折りたたむ。
276
+ * @param path - 折りたたむディレクトリの絶対パス
277
+ */
278
+ collapse(path: string): void;
279
+ /**
280
+ * 展開/折りたたみをトグルする。
281
+ * @param path - 対象ディレクトリの絶対パス
282
+ */
283
+ toggleExpand(path: string): Promise<void>;
284
+ /**
285
+ * 指定パスまでの祖先ディレクトリをすべて展開する。
286
+ * ファイルを可視状態にしたい場合に使用する。
287
+ * @param path - 表示したいノードの絶対パス
288
+ */
289
+ expandTo(path: string): Promise<void>;
290
+ /**
291
+ * ノードを選択する。
292
+ * @param path - 選択するノードの絶対パス
293
+ * @param mode - 選択モード。'replace'=単一選択、'toggle'=Ctrl+Click、'range'=Shift+Click
294
+ */
295
+ select(path: string, mode?: 'replace' | 'toggle' | 'range'): void;
296
+ /** flatList内の全ノードを選択する */
297
+ selectAll(): void;
298
+ /** 選択をすべて解除する */
299
+ clearSelection(): void;
300
+ /**
301
+ * リネームモードを開始する。UIはこの状態を検知してインライン編集UIを表示する。
302
+ * @param path - リネーム対象の絶対パス
303
+ */
304
+ startRename(path: string): void;
305
+ /**
306
+ * リネームを確定する。adapter.renameが呼ばれ、親ディレクトリがリフレッシュされる。
307
+ * @param newName - 新しいファイル名(パスではなく名前のみ)
308
+ */
309
+ commitRename(newName: string): Promise<void>;
310
+ /** リネームをキャンセルする */
311
+ cancelRename(): void;
312
+ /**
313
+ * インライン新規作成モードを開始する。親ディレクトリを展開し、入力行を表示する。
314
+ * @param parentPath - 作成先の親ディレクトリの絶対パス
315
+ * @param isDirectory - フォルダを作成する場合はtrue
316
+ * @param insertAfterPath - この位置の直後に入力行を表示する
317
+ */
318
+ startCreate(parentPath: string, isDirectory: boolean, insertAfterPath?: string): Promise<void>;
319
+ /**
320
+ * インライン新規作成を確定する。adapter.createFile/createDirが呼ばれる。
321
+ * @param name - 新規ファイル/フォルダ名
322
+ */
323
+ commitCreate(name: string): Promise<void>;
324
+ /** インライン新規作成をキャンセルする */
325
+ cancelCreate(): void;
326
+ /**
327
+ * ファイルを新規作成する。adapter.createFileが呼ばれる。
328
+ * @param parentPath - 作成先の親ディレクトリの絶対パス
329
+ * @param name - 新規ファイル名
330
+ */
331
+ createFile(parentPath: string, name: string): Promise<void>;
332
+ /**
333
+ * フォルダを新規作成する。adapter.createDirが呼ばれる。
334
+ * @param parentPath - 作成先の親ディレクトリの絶対パス
335
+ * @param name - 新規フォルダ名
336
+ */
337
+ createDir(parentPath: string, name: string): Promise<void>;
338
+ /** 選択中のアイテムをすべて削除する。adapter.deleteが呼ばれる */
339
+ deleteSelected(): Promise<void>;
340
+ /**
341
+ * ツリーを再読み込みする。展開状態は保持される。
342
+ * @param path - リフレッシュ対象のディレクトリ。省略時はルート全体
343
+ */
344
+ refresh(path?: string): Promise<void>;
345
+ /**
346
+ * ファジー検索クエリを設定する。flatListがフィルタされマッチしたノードのみ表示される。
347
+ * @param query - 検索文字列。nullで検索解除
348
+ */
349
+ setSearchQuery(query: string | null): void;
350
+ /**
351
+ * ツリー配下の全ファイルを再帰的に収集する。
352
+ * fuzzyFind等で全ファイル検索を行う際の入力データとして使用する。
353
+ * @returns 全FileEntryの配列
354
+ */
355
+ collectAllFiles(): Promise<FileEntry[]>;
356
+ /**
357
+ * フィルタ関数を動的に変更する。既存ノードにも即座に適用される。
358
+ * @param fn - フィルタ関数。nullでフィルタ解除
359
+ */
360
+ setFilter(fn: ((entry: FileEntry) => boolean) | null): void;
361
+ /**
362
+ * ソート関数を動的に変更する。既存ノードにも即座に適用される。
363
+ * @param fn - ソート関数。nullでデフォルトソートに戻す
364
+ */
365
+ setSort(fn: ((a: FileEntry, b: FileEntry) => number) | null): void;
366
+ /** コントローラを破棄する。ファイル監視を停止し、購読をすべて解除する */
367
+ destroy(): void;
368
+ }
369
+
370
+ /** {@link TreeProvider} に渡すprops */
371
+ interface TreeProviderProps {
372
+ /** ファイルシステム操作の実装 */
373
+ adapter: FileSystemAdapter;
374
+ /** ツリーのルートディレクトリパス */
375
+ rootPath: string;
376
+ /** ファイル/フォルダのソート関数 */
377
+ sort?: FileTreeOptions['sort'];
378
+ /** 表示対象を絞り込むフィルタ関数 */
379
+ filter?: FileTreeOptions['filter'];
380
+ /** ファイル監視の設定 */
381
+ watchOptions?: FileTreeOptions['watchOptions'];
382
+ /** ツリー操作イベントのコールバック */
383
+ onEvent?: (event: TreeEvent) => void;
384
+ children: ReactNode;
385
+ }
386
+ /**
387
+ * ファイルツリーのコンテキストプロバイダー。
388
+ * 内部で `createFileTree` を呼び出し、マウント時に `loadRoot` でルートを読み込む。
389
+ * 子コンポーネントから `useFileTree` / `useTreeNode` でツリー状態にアクセスできる。
390
+ */
391
+ declare function TreeProvider({ adapter, rootPath, sort, filter, watchOptions, onEvent, children, }: TreeProviderProps): React__default.JSX.Element;
392
+
393
+ /** TreeProvider が提供するコンテキスト値 */
394
+ interface TreeContextValue {
395
+ /** ツリー操作用コントローラ */
396
+ controller: FileTreeController;
397
+ /** 現在のツリー状態 */
398
+ state: TreeState;
399
+ }
400
+ /** @internal ツリーコンテキスト。通常は直接使わず `useTreeContext` 経由で利用する */
401
+ declare const TreeContext: React$1.Context<TreeContextValue | null>;
402
+ /**
403
+ * TreeProvider のコンテキストを取得する。
404
+ * TreeProvider の外で使用すると Error をスローする。
405
+ * @returns ツリーのコントローラと状態
406
+ */
407
+ declare function useTreeContext(): TreeContextValue;
408
+
409
+ /** {@link useFileTree} の戻り値。ツリー状態の全フィールドに加えコントローラを含む */
410
+ interface UseFileTreeResult extends TreeState {
411
+ /** ツリー操作用コントローラ */
412
+ controller: FileTreeController;
413
+ }
414
+ /**
415
+ * ツリー全体の状態とコントローラを返すフック。
416
+ * TreeProvider 内で使用すること。
417
+ * @returns ツリー状態(flatList, expandedPaths 等)とコントローラ
418
+ */
419
+ declare function useFileTree(): UseFileTreeResult;
420
+
421
+ /** {@link useTreeNode} の戻り値 */
422
+ interface UseTreeNodeResult {
423
+ /** ノード本体 */
424
+ node: TreeNode;
425
+ /** ディレクトリが展開されているか */
426
+ isExpanded: boolean;
427
+ /** 選択されているか */
428
+ isSelected: boolean;
429
+ /** リネーム中か */
430
+ isRenaming: boolean;
431
+ /** ツリー上のネスト深度 */
432
+ depth: number;
433
+ }
434
+ /**
435
+ * 指定パスのノード状態を返すフック。
436
+ * TreeProvider 内で使用すること。パスが見つからない場合は `null` を返す。
437
+ * @param path - 取得したいノードのファイルパス
438
+ * @returns ノードの状態。パスが見つからなければ `null`
439
+ */
440
+ declare function useTreeNode(path: string): UseTreeNodeResult | null;
441
+
442
+ /** コンテキストメニューの表示状態 */
443
+ interface ContextMenuState {
444
+ /** メニューが表示されているか */
445
+ isVisible: boolean;
446
+ /** メニューのX座標(clientX) */
447
+ x: number;
448
+ /** メニューのY座標(clientY) */
449
+ y: number;
450
+ /** 右クリック対象のファイルパス */
451
+ targetPath: string | null;
452
+ }
453
+ /** {@link useContextMenu} の戻り値 */
454
+ interface UseContextMenuResult extends ContextMenuState {
455
+ /** 指定位置にコンテキストメニューを表示する */
456
+ show(e: React.MouseEvent, targetPath: string): void;
457
+ /** コンテキストメニューを閉じる */
458
+ hide(): void;
459
+ }
460
+ /**
461
+ * 右クリックメニューの表示位置・表示状態を管理するフック。
462
+ * @returns メニュー状態と表示/非表示の操作関数
463
+ */
464
+ declare function useContextMenu(): UseContextMenuResult;
465
+
466
+ /**
467
+ * InputService(momoi-keybind)を受け取り、エクスプローラーのコマンドハンドラーを登録する。
468
+ * momoi-keybindが無い場合は使わなくてOK。
469
+ *
470
+ * @param inputService - momoi-keybindのInputServiceインスタンス。nullならスキップ
471
+ * @param options - コマンド実行時の追加処理
472
+ */
473
+ declare function useExplorerKeybindings(inputService: InputServiceLike | null, options?: {
474
+ onCopyPath?: (paths: string[]) => void;
475
+ }): void;
476
+ /**
477
+ * momoi-keybindのInputServiceの最小インターフェース。
478
+ * momoi-keybindに直接依存せず、duck typingで受け入れる。
479
+ */
480
+ interface InputServiceLike {
481
+ registerCommand(command: string, handler: (args?: unknown) => void): () => void;
482
+ setContext(key: string, value: unknown): void;
483
+ deleteContext(key: string): void;
484
+ }
485
+
486
+ /**
487
+ * エクスプローラーのフォーカス状態をmomoi-keybindのコンテキストに連動させるhook。
488
+ * 返されたpropsをエクスプローラーのルート要素に渡す。
489
+ *
490
+ * @param inputService - momoi-keybindのInputServiceインスタンス。nullならno-op
491
+ * @param contextKey - コンテキストキー名。デフォルト: 'explorerFocus'
492
+ */
493
+ declare function useExplorerFocus(inputService: InputServiceLike | null, contextKey?: string): {
494
+ onFocus: () => void;
495
+ onBlur: () => void;
496
+ tabIndex: number;
497
+ };
498
+
499
+ export { type ContextMenuState, type InputServiceLike, TreeContext, type TreeContextValue, TreeProvider, type TreeProviderProps, type UseContextMenuResult, type UseFileTreeResult, type UseTreeNodeResult, useContextMenu, useExplorerFocus, useExplorerKeybindings, useFileTree, useTreeContext, useTreeNode };