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 @@
1
+ {"version":3,"sources":["../src/react/TreeProvider.tsx","../src/core/event-processor.ts","../src/core/flatten.ts","../src/core/selection.ts","../src/core/sort.ts","../src/core/filter.ts","../src/core/search.ts","../src/core/tree.ts","../src/react/context.ts","../src/react/useFileTree.ts","../src/react/useTreeNode.ts","../src/react/useContextMenu.ts","../src/react/useExplorerKeybindings.ts","../src/core/keybindings.ts","../src/react/useExplorerFocus.ts"],"sourcesContent":["import type React from 'react'\r\nimport { useEffect, useMemo, useRef, useState } from 'react'\r\nimport type { FileSystemAdapter, FileTreeController, FileTreeOptions, TreeEvent, TreeState } from '../core/types'\r\nimport { createFileTree } from '../core/tree'\r\nimport { TreeContext } from './context'\r\nimport type { ReactNode } from 'react'\r\n\r\n/** {@link TreeProvider} に渡すprops */\r\nexport interface TreeProviderProps {\r\n /** ファイルシステム操作の実装 */\r\n adapter: FileSystemAdapter\r\n /** ツリーのルートディレクトリパス */\r\n rootPath: string\r\n /** ファイル/フォルダのソート関数 */\r\n sort?: FileTreeOptions['sort']\r\n /** 表示対象を絞り込むフィルタ関数 */\r\n filter?: FileTreeOptions['filter']\r\n /** ファイル監視の設定 */\r\n watchOptions?: FileTreeOptions['watchOptions']\r\n /** ツリー操作イベントのコールバック */\r\n onEvent?: (event: TreeEvent) => void\r\n children: ReactNode\r\n}\r\n\r\n/**\r\n * ファイルツリーのコンテキストプロバイダー。\r\n * 内部で `createFileTree` を呼び出し、マウント時に `loadRoot` でルートを読み込む。\r\n * 子コンポーネントから `useFileTree` / `useTreeNode` でツリー状態にアクセスできる。\r\n */\r\nexport function TreeProvider({\r\n adapter,\r\n rootPath,\r\n sort,\r\n filter,\r\n watchOptions,\r\n onEvent,\r\n children,\r\n}: TreeProviderProps): React.JSX.Element {\r\n const onEventRef = useRef(onEvent)\r\n onEventRef.current = onEvent\r\n\r\n const controller = useMemo<FileTreeController>(() => {\r\n return createFileTree({\r\n adapter,\r\n rootPath,\r\n sort,\r\n filter,\r\n watchOptions,\r\n onEvent: (event) => onEventRef.current?.(event),\r\n })\r\n }, [adapter, rootPath])\r\n\r\n const [state, setState] = useState<TreeState>(() => controller.getState())\r\n\r\n useEffect(() => {\r\n const unsub = controller.subscribe(setState)\r\n controller.loadRoot()\r\n return () => {\r\n unsub()\r\n controller.destroy()\r\n }\r\n }, [controller])\r\n\r\n const value = useMemo(() => ({ controller, state }), [controller, state])\r\n\r\n return (\r\n <TreeContext.Provider value={value}>\r\n {children}\r\n </TreeContext.Provider>\r\n )\r\n}\r\n","// イベント処理ユーティリティ\r\n//\r\n// VSCode準拠のデバウンス・イベント合体・スロットリング\r\n\r\nimport type { RawWatchEvent, WatchEvent, WatchOptions } from './types'\r\n\r\nconst DEFAULT_DEBOUNCE_MS = 75\r\nconst DEFAULT_THROTTLE_CHUNK_SIZE = 500\r\nconst DEFAULT_THROTTLE_DELAY_MS = 200\r\n\r\n/**\r\n * 生イベントを合体する(VSCode EventCoalescer 準拠)。\r\n * - rename → delete(old) + create(new) に分解\r\n * - delete + create(同一パス) → modify に合体\r\n * - create + modify(同一パス) → create を維持\r\n * - 親フォルダ delete → 子の delete を除去\r\n *\r\n * @param raw - アダプタから受け取った生イベントの配列\r\n * @returns 合体処理後のWatchEvent配列\r\n */\r\nexport function coalesceEvents(raw: RawWatchEvent[]): WatchEvent[] {\r\n // rename を分解\r\n const expanded: WatchEvent[] = []\r\n for (const event of raw) {\r\n if (event.type === 'rename' && event.newPath) {\r\n expanded.push({ type: 'delete', path: event.path, isDirectory: event.isDirectory })\r\n expanded.push({ type: 'create', path: event.newPath, isDirectory: event.isDirectory })\r\n } else {\r\n expanded.push({ type: event.type as WatchEvent['type'], path: event.path, isDirectory: event.isDirectory })\r\n }\r\n }\r\n\r\n // 同一パスのイベントを合体\r\n const byPath = new Map<string, WatchEvent>()\r\n for (const event of expanded) {\r\n const existing = byPath.get(event.path)\r\n if (!existing) {\r\n byPath.set(event.path, event)\r\n continue\r\n }\r\n\r\n // delete + create → modify\r\n if (existing.type === 'delete' && event.type === 'create') {\r\n byPath.set(event.path, { type: 'modify', path: event.path, isDirectory: event.isDirectory })\r\n continue\r\n }\r\n\r\n // create + modify → create を維持\r\n if (existing.type === 'create' && event.type === 'modify') {\r\n continue\r\n }\r\n\r\n // create + delete → 相殺して除去\r\n if (existing.type === 'create' && event.type === 'delete') {\r\n byPath.delete(event.path)\r\n continue\r\n }\r\n\r\n // それ以外は後勝ち\r\n byPath.set(event.path, event)\r\n }\r\n\r\n const result = Array.from(byPath.values())\r\n\r\n // 親フォルダ delete がある場合、子の delete を除去\r\n const deletedDirs = new Set<string>()\r\n for (const event of result) {\r\n if (event.type === 'delete' && event.isDirectory) {\r\n deletedDirs.add(event.path)\r\n }\r\n }\r\n\r\n if (deletedDirs.size === 0) return result\r\n\r\n return result.filter((event) => {\r\n if (event.type !== 'delete') return true\r\n for (const dir of deletedDirs) {\r\n if (event.path !== dir && event.path.startsWith(dir + '/')) {\r\n return false\r\n }\r\n }\r\n return true\r\n })\r\n}\r\n\r\n/**\r\n * イベントプロセッサを生成する。\r\n * デバウンス → 合体 → スロットリング → コールバック のパイプラインで処理する。\r\n *\r\n * @param callback - 処理済みイベントを受け取るコールバック\r\n * @param options - デバウンス・合体・スロットリングの設定\r\n * @returns push/flush/destroyメソッドを持つプロセッサオブジェクト\r\n */\r\nexport function createEventProcessor(\r\n callback: (events: WatchEvent[]) => void,\r\n options: WatchOptions = {},\r\n): {\r\n push(events: RawWatchEvent[]): void\r\n flush(): void\r\n destroy(): void\r\n} {\r\n const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS\r\n const shouldCoalesce = options.coalesce ?? true\r\n const throttleChunkSize = options.throttle?.maxChunkSize ?? DEFAULT_THROTTLE_CHUNK_SIZE\r\n const throttleDelayMs = options.throttle?.delayMs ?? DEFAULT_THROTTLE_DELAY_MS\r\n\r\n let buffer: RawWatchEvent[] = []\r\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\r\n let throttleTimer: ReturnType<typeof setTimeout> | null = null\r\n let destroyed = false\r\n\r\n function processBuffer(): void {\r\n if (destroyed || buffer.length === 0) return\r\n\r\n const raw = buffer\r\n buffer = []\r\n\r\n const events = shouldCoalesce ? coalesceEvents(raw) : raw.map((e) => ({\r\n type: e.type === 'rename' ? 'modify' as const : e.type as WatchEvent['type'],\r\n path: e.type === 'rename' && e.newPath ? e.newPath : e.path,\r\n isDirectory: e.isDirectory,\r\n }))\r\n\r\n if (events.length === 0) return\r\n\r\n // スロットリング: 大量イベントをチャンクに分割\r\n if (events.length <= throttleChunkSize) {\r\n callback(events)\r\n return\r\n }\r\n\r\n let offset = 0\r\n function emitChunk(): void {\r\n if (destroyed || offset >= events.length) return\r\n const chunk = events.slice(offset, offset + throttleChunkSize)\r\n offset += throttleChunkSize\r\n callback(chunk)\r\n if (offset < events.length) {\r\n throttleTimer = setTimeout(emitChunk, throttleDelayMs)\r\n }\r\n }\r\n emitChunk()\r\n }\r\n\r\n return {\r\n push(events: RawWatchEvent[]): void {\r\n if (destroyed) return\r\n buffer.push(...events)\r\n if (debounceTimer !== null) {\r\n clearTimeout(debounceTimer)\r\n }\r\n debounceTimer = setTimeout(processBuffer, debounceMs)\r\n },\r\n\r\n flush(): void {\r\n if (debounceTimer !== null) {\r\n clearTimeout(debounceTimer)\r\n debounceTimer = null\r\n }\r\n processBuffer()\r\n },\r\n\r\n destroy(): void {\r\n destroyed = true\r\n if (debounceTimer !== null) clearTimeout(debounceTimer)\r\n if (throttleTimer !== null) clearTimeout(throttleTimer)\r\n buffer = []\r\n },\r\n }\r\n}\r\n","// ツリー → フラットリスト変換(仮想スクロール用)\n\nimport type { FlatNode, TreeNode } from './types'\n\n/**\n * 展開されたツリーをフラットリストに変換する。\n * react-virtuoso等の仮想スクロールに渡すためのもの。\n * matchingPaths が指定された場合、そこに含まれるノードのみ表示する。\n */\nexport function flattenTree(\n nodes: TreeNode[],\n expandedPaths: Set<string>,\n matchingPaths?: Set<string> | null,\n): FlatNode[] {\n const result: FlatNode[] = []\n\n function walk(children: TreeNode[], depth: number): void {\n for (const node of children) {\n if (matchingPaths && !matchingPaths.has(node.path)) continue\n\n result.push({ node, depth })\n if (node.isDirectory && node.children) {\n // 検索中はマッチしたディレクトリを自動展開\n if (matchingPaths || expandedPaths.has(node.path)) {\n walk(node.children, depth + 1)\n }\n }\n }\n }\n\n walk(nodes, 0)\n return result\n}\n","// 選択管理(単一/複数/範囲)\n\nimport type { FlatNode } from './types'\n\n/**\n * replace: 既存選択をクリアして新しいパスのみ選択\n * toggle: 指定パスの選択を反転(Ctrl+Click)\n * range: anchorから指定パスまでの範囲を選択(Shift+Click)\n */\nexport function computeSelection(\n currentSelected: Set<string>,\n anchorPath: string | null,\n targetPath: string,\n mode: 'replace' | 'toggle' | 'range',\n flatList: FlatNode[],\n): { selectedPaths: Set<string>; anchorPath: string } {\n switch (mode) {\n case 'replace':\n return {\n selectedPaths: new Set([targetPath]),\n anchorPath: targetPath,\n }\n\n case 'toggle': {\n const next = new Set(currentSelected)\n if (next.has(targetPath)) {\n next.delete(targetPath)\n } else {\n next.add(targetPath)\n }\n return {\n selectedPaths: next,\n anchorPath: targetPath,\n }\n }\n\n case 'range': {\n if (!anchorPath) {\n return {\n selectedPaths: new Set([targetPath]),\n anchorPath: targetPath,\n }\n }\n\n const paths = flatList.map((f) => f.node.path)\n const anchorIdx = paths.indexOf(anchorPath)\n const targetIdx = paths.indexOf(targetPath)\n\n if (anchorIdx === -1 || targetIdx === -1) {\n return {\n selectedPaths: new Set([targetPath]),\n anchorPath: targetPath,\n }\n }\n\n const start = Math.min(anchorIdx, targetIdx)\n const end = Math.max(anchorIdx, targetIdx)\n const rangePaths = new Set(paths.slice(start, end + 1))\n\n return {\n selectedPaths: rangePaths,\n anchorPath, // range選択ではanchorは変えない\n }\n }\n }\n}\n","// ソート\n\nimport type { FileEntry } from './types'\n\n/**\n * デフォルトソート: フォルダ優先 → 名前の大文字小文字無視で昇順\n */\nexport function defaultSort(a: FileEntry, b: FileEntry): number {\n if (a.isDirectory !== b.isDirectory) {\n return a.isDirectory ? -1 : 1\n }\n return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })\n}\n","// フィルタ\n\nimport type { FileEntry } from './types'\n\n/**\n * デフォルトフィルタ: 全表示(フィルタなし)\n */\nexport function defaultFilter(_entry: FileEntry): boolean {\n return true\n}\n","// ファイル名検索ユーティリティ\n\nimport type { FileEntry, TreeNode } from './types'\n\n/**\n * ファジーマッチ: クエリの各文字が順番に含まれているかチェック\n * スコアも返す(連続マッチ・先頭マッチ・セパレータ後マッチが高スコア)\n */\nexport function fuzzyMatch(query: string, target: string): { match: boolean; score: number } {\n const q = query.toLowerCase()\n const t = target.toLowerCase()\n\n if (q.length === 0) return { match: true, score: 0 }\n if (q.length > t.length) return { match: false, score: 0 }\n\n // 完全一致は最高スコア\n if (t === q) return { match: true, score: 100 }\n\n // 前方一致は高スコア\n if (t.startsWith(q)) return { match: true, score: 90 }\n\n // 部分一致\n if (t.includes(q)) return { match: true, score: 80 }\n\n // ファジーマッチ\n let qi = 0\n let score = 0\n let lastMatchIndex = -2\n\n for (let ti = 0; ti < t.length && qi < q.length; ti++) {\n if (t[ti] === q[qi]) {\n qi++\n // 連続マッチボーナス\n if (ti === lastMatchIndex + 1) {\n score += 5\n }\n // セパレータ直後のマッチボーナス(パス区切り、ハイフン、アンダースコア等)\n if (ti === 0 || '/\\\\-_.'.includes(t[ti - 1])) {\n score += 10\n }\n score += 1\n lastMatchIndex = ti\n }\n }\n\n if (qi < q.length) return { match: false, score: 0 }\n return { match: true, score }\n}\n\n/**\n * ツリーフィルタ用: クエリにマッチするノードとその祖先を残す\n * マッチしたパスのSetを返す\n */\nexport function findMatchingPaths(\n nodes: TreeNode[],\n query: string,\n): Set<string> {\n const matching = new Set<string>()\n\n function walk(node: TreeNode, ancestors: string[]): boolean {\n const nameMatch = fuzzyMatch(query, node.name).match\n let childMatch = false\n\n if (node.children) {\n for (const child of node.children) {\n if (walk(child, [...ancestors, node.path])) {\n childMatch = true\n }\n }\n }\n\n if (nameMatch || childMatch) {\n matching.add(node.path)\n for (const a of ancestors) {\n matching.add(a)\n }\n return true\n }\n return false\n }\n\n for (const node of nodes) {\n walk(node, [])\n }\n\n return matching\n}\n\n/**\n * ファジーファインド用: 全ファイルからスコア順に候補を返す\n */\nexport function fuzzyFind(\n files: FileEntry[],\n query: string,\n maxResults: number = 50,\n): FileEntry[] {\n if (!query) return []\n\n const scored: Array<{ entry: FileEntry; score: number }> = []\n\n for (const entry of files) {\n // ファイル名でマッチ\n const nameResult = fuzzyMatch(query, entry.name)\n // パスでもマッチ(スコアは低め)\n const pathResult = fuzzyMatch(query, entry.path)\n\n const bestScore = Math.max(nameResult.score, pathResult.score * 0.5)\n\n if (nameResult.match || pathResult.match) {\n scored.push({ entry, score: bestScore })\n }\n }\n\n scored.sort((a, b) => b.score - a.score)\n return scored.slice(0, maxResults).map((s) => s.entry)\n}\n","// createFileTree - ヘッドレスファイルツリーコントローラ\r\n\r\nimport type {\r\n FileEntry,\r\n FileSystemAdapter,\r\n FileTreeController,\r\n FileTreeOptions,\r\n FlatNode,\r\n TreeEvent,\r\n TreeNode,\r\n TreeState,\r\n WatchEvent,\r\n} from './types'\r\nimport { createEventProcessor } from './event-processor'\r\nimport { flattenTree } from './flatten'\r\nimport { computeSelection } from './selection'\r\nimport { defaultSort } from './sort'\r\nimport { defaultFilter } from './filter'\r\nimport { findMatchingPaths } from './search'\r\n\r\nfunction toTreeNode(entry: FileEntry, depth: number): TreeNode {\r\n return {\r\n ...entry,\r\n depth,\r\n children: entry.isDirectory ? undefined : undefined,\r\n childrenLoaded: false,\r\n }\r\n}\r\n\r\nfunction findNode(nodes: TreeNode[], path: string): TreeNode | undefined {\r\n for (const node of nodes) {\r\n if (node.path === path) return node\r\n if (node.children) {\r\n const found = findNode(node.children, path)\r\n if (found) return found\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\nfunction findParentNodes(nodes: TreeNode[], targetPath: string): TreeNode | undefined {\r\n for (const node of nodes) {\r\n if (node.children) {\r\n for (const child of node.children) {\r\n if (child.path === targetPath) return node\r\n }\r\n const found = findParentNodes(node.children, targetPath)\r\n if (found) return found\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\n/** パスの親ディレクトリを取得 */\r\nfunction dirname(path: string): string {\r\n const sep = path.includes('\\\\') ? '\\\\' : '/'\r\n const idx = path.lastIndexOf(sep)\r\n return idx === -1 ? '' : path.slice(0, idx)\r\n}\r\n\r\n/** パスの末尾(ファイル名)を取得 */\r\nfunction basename(path: string): string {\r\n const sep = path.includes('\\\\') ? '\\\\' : '/'\r\n const idx = path.lastIndexOf(sep)\r\n return idx === -1 ? path : path.slice(idx + 1)\r\n}\r\n\r\n/**\r\n * ヘッドレスファイルツリーコントローラを生成する。\r\n * フレームワーク非依存。React等で使う場合は `momoi-explorer/react` のTreeProviderを推奨。\r\n *\r\n * @param options - ツリーの初期化オプション\r\n * @returns FileTreeController インスタンス\r\n *\r\n * @example\r\n * ```ts\r\n * import { createFileTree } from 'momoi-explorer'\r\n *\r\n * const tree = createFileTree({\r\n * adapter: myAdapter,\r\n * rootPath: '/home/user/project',\r\n * onEvent: (e) => console.log(e),\r\n * })\r\n * await tree.loadRoot()\r\n * ```\r\n */\r\nexport function createFileTree(options: FileTreeOptions): FileTreeController {\r\n const { adapter, rootPath, onEvent } = options\r\n let sortFn = options.sort ?? defaultSort\r\n let filterFn = options.filter ?? defaultFilter\r\n\r\n let state: TreeState = {\r\n rootPath,\r\n rootNodes: [],\r\n expandedPaths: new Set(),\r\n selectedPaths: new Set(),\r\n anchorPath: null,\r\n renamingPath: null,\r\n creatingState: null,\r\n searchQuery: null,\r\n flatList: [],\r\n }\r\n\r\n const listeners = new Set<(state: TreeState) => void>()\r\n let expandingPaths = new Set<string>()\r\n\r\n function emit(event: TreeEvent): void {\r\n onEvent?.(event)\r\n }\r\n\r\n function notify(): void {\r\n const matchingPaths = state.searchQuery\r\n ? findMatchingPaths(state.rootNodes, state.searchQuery)\r\n : null\r\n state = { ...state, flatList: flattenTree(state.rootNodes, state.expandedPaths, matchingPaths) }\r\n for (const listener of listeners) {\r\n listener(state)\r\n }\r\n }\r\n\r\n async function loadChildren(node: TreeNode): Promise<void> {\r\n const entries = await adapter.readDir(node.path)\r\n const filtered = entries.filter(filterFn)\r\n filtered.sort(sortFn)\r\n // 既存の展開済み子ノードのchildrenを引き継ぐ\r\n const oldChildMap = node.children\r\n ? new Map(node.children.map((c) => [c.path, c]))\r\n : new Map<string, TreeNode>()\r\n node.children = filtered.map((e) => {\r\n const existing = oldChildMap.get(e.path)\r\n if (existing && existing.childrenLoaded) {\r\n return { ...toTreeNode(e, node.depth + 1), children: existing.children, childrenLoaded: true }\r\n }\r\n return toTreeNode(e, node.depth + 1)\r\n })\r\n node.childrenLoaded = true\r\n }\r\n\r\n /** 親ディレクトリをリフレッシュし、展開中のフォルダの子も再読み込みする */\r\n async function refreshParent(parentPath: string): Promise<void> {\r\n const parentNode = findNode(state.rootNodes, parentPath)\r\n if (parentNode) {\r\n await loadChildren(parentNode)\r\n state.expandedPaths = new Set(state.expandedPaths)\r\n state.expandedPaths.add(parentPath)\r\n } else if (parentPath === rootPath) {\r\n const entries = await adapter.readDir(rootPath)\r\n const filtered = entries.filter(filterFn)\r\n filtered.sort(sortFn)\r\n // 既存の展開済みノードのchildrenを引き継ぐ\r\n const oldNodeMap = new Map(state.rootNodes.map((n) => [n.path, n]))\r\n state.rootNodes = filtered.map((e) => {\r\n const existing = oldNodeMap.get(e.path)\r\n if (existing && existing.childrenLoaded) {\r\n return { ...toTreeNode(e, 0), children: existing.children, childrenLoaded: true }\r\n }\r\n return toTreeNode(e, 0)\r\n })\r\n }\r\n }\r\n\r\n function sortNodes(nodes: TreeNode[]): void {\r\n nodes.sort(sortFn)\r\n for (const node of nodes) {\r\n if (node.children) {\r\n sortNodes(node.children)\r\n }\r\n }\r\n }\r\n\r\n function filterNodes(nodes: TreeNode[]): TreeNode[] {\r\n return nodes.filter((node) => {\r\n if (!filterFn(node)) return false\r\n if (node.children) {\r\n node.children = filterNodes(node.children)\r\n }\r\n return true\r\n })\r\n }\r\n\r\n // ウォッチ関連\r\n let unwatchFn: (() => void) | null = null\r\n let eventProcessor: ReturnType<typeof createEventProcessor> | null = null\r\n\r\n function handleWatchEvents(events: WatchEvent[]): void {\r\n emit({ type: 'external-change', changes: events })\r\n\r\n // 変更されたパスの親ディレクトリを収集してリフレッシュ\r\n const dirsToRefresh = new Set<string>()\r\n for (const event of events) {\r\n const parent = dirname(event.path)\r\n // 展開中のディレクトリのみリフレッシュ\r\n if (parent === rootPath || state.expandedPaths.has(parent)) {\r\n dirsToRefresh.add(parent)\r\n }\r\n // ディレクトリ自体が変更された場合、展開中ならそれもリフレッシュ\r\n if (event.isDirectory && state.expandedPaths.has(event.path)) {\r\n dirsToRefresh.add(event.path)\r\n }\r\n }\r\n\r\n // 非同期でリフレッシュ(展開状態を保持)\r\n for (const dir of dirsToRefresh) {\r\n refreshParent(dir).then(() => notify()).catch(() => {})\r\n }\r\n }\r\n\r\n function startWatching(): void {\r\n if (!adapter.watch) return\r\n\r\n eventProcessor = createEventProcessor(handleWatchEvents, options.watchOptions)\r\n unwatchFn = adapter.watch(rootPath, (events) => {\r\n eventProcessor!.push(events)\r\n })\r\n }\r\n\r\n function stopWatching(): void {\r\n if (unwatchFn) {\r\n unwatchFn()\r\n unwatchFn = null\r\n }\r\n if (eventProcessor) {\r\n eventProcessor.destroy()\r\n eventProcessor = null\r\n }\r\n }\r\n\r\n const controller: FileTreeController = {\r\n getState(): TreeState {\r\n return state\r\n },\r\n\r\n subscribe(listener: (s: TreeState) => void): () => void {\r\n listeners.add(listener)\r\n return () => listeners.delete(listener)\r\n },\r\n\r\n async loadRoot(): Promise<void> {\r\n const entries = await adapter.readDir(rootPath)\r\n const filtered = entries.filter(filterFn)\r\n filtered.sort(sortFn)\r\n state.rootNodes = filtered.map((e) => toTreeNode(e, 0))\r\n notify()\r\n startWatching()\r\n },\r\n\r\n async expand(path: string): Promise<void> {\r\n // 再入ガード: 同じパスの展開処理が進行中なら無視\r\n if (expandingPaths.has(path)) return\r\n expandingPaths.add(path)\r\n\r\n try {\r\n const node = findNode(state.rootNodes, path)\r\n if (!node || !node.isDirectory) return\r\n\r\n if (!node.childrenLoaded) {\r\n await loadChildren(node)\r\n }\r\n\r\n // await後にstateが変わっている可能性があるので再確認\r\n state.expandedPaths = new Set(state.expandedPaths)\r\n state.expandedPaths.add(path)\r\n notify()\r\n emit({ type: 'expand', path })\r\n } finally {\r\n expandingPaths.delete(path)\r\n }\r\n },\r\n\r\n collapse(path: string): void {\r\n const sep = path.includes('\\\\') ? '\\\\' : '/'\r\n const prefix = path + sep\r\n state.expandedPaths = new Set(state.expandedPaths)\r\n state.expandedPaths.delete(path)\r\n // 子孫の展開状態もクリア\r\n for (const p of state.expandedPaths) {\r\n if (p.startsWith(prefix)) {\r\n state.expandedPaths.delete(p)\r\n }\r\n }\r\n notify()\r\n emit({ type: 'collapse', path })\r\n },\r\n\r\n async toggleExpand(path: string): Promise<void> {\r\n // expand進行中は無視(ダブルクリックで展開→即collapseを防止)\r\n if (expandingPaths.has(path)) return\r\n\r\n if (state.expandedPaths.has(path)) {\r\n controller.collapse(path)\r\n } else {\r\n await controller.expand(path)\r\n }\r\n },\r\n\r\n async expandTo(path: string): Promise<void> {\r\n // パスの各祖先を展開していく\r\n const parts: string[] = []\r\n let current = path\r\n while (current !== rootPath && current !== '') {\r\n const parent = dirname(current)\r\n if (parent === current) break\r\n parts.unshift(parent)\r\n current = parent\r\n }\r\n\r\n for (const ancestorPath of parts) {\r\n if (ancestorPath === rootPath) continue\r\n if (!state.expandedPaths.has(ancestorPath)) {\r\n await controller.expand(ancestorPath)\r\n }\r\n }\r\n },\r\n\r\n select(path: string, mode: 'replace' | 'toggle' | 'range' = 'replace'): void {\r\n const result = computeSelection(\r\n state.selectedPaths,\r\n state.anchorPath,\r\n path,\r\n mode,\r\n state.flatList,\r\n )\r\n state.selectedPaths = result.selectedPaths\r\n state.anchorPath = result.anchorPath\r\n notify()\r\n emit({ type: 'select', paths: Array.from(result.selectedPaths) })\r\n },\r\n\r\n selectAll(): void {\r\n state.selectedPaths = new Set(state.flatList.map((f) => f.node.path))\r\n notify()\r\n emit({ type: 'select', paths: Array.from(state.selectedPaths) })\r\n },\r\n\r\n clearSelection(): void {\r\n state.selectedPaths = new Set()\r\n state.anchorPath = null\r\n notify()\r\n emit({ type: 'select', paths: [] })\r\n },\r\n\r\n startRename(path: string): void {\r\n state.renamingPath = path\r\n notify()\r\n },\r\n\r\n async commitRename(newName: string): Promise<void> {\r\n if (!state.renamingPath || !adapter.rename) return\r\n\r\n const oldPath = state.renamingPath\r\n const parent = dirname(oldPath)\r\n const sep = oldPath.includes('\\\\') ? '\\\\' : '/'\r\n const newPath = parent + sep + newName\r\n\r\n await adapter.rename(oldPath, newPath)\r\n state.renamingPath = null\r\n\r\n // リネームされたノードの親をリフレッシュ\r\n if (parent === rootPath) {\r\n await controller.loadRoot()\r\n } else {\r\n const parentNode = findNode(state.rootNodes, parent)\r\n if (parentNode) {\r\n await loadChildren(parentNode)\r\n }\r\n }\r\n\r\n notify()\r\n emit({ type: 'rename', oldPath, newPath })\r\n },\r\n\r\n cancelRename(): void {\r\n state.renamingPath = null\r\n notify()\r\n },\r\n\r\n async startCreate(parentPath: string, isDirectory: boolean, insertAfterPath?: string): Promise<void> {\r\n // 親フォルダを展開してから作成モードに入る\r\n if (parentPath !== rootPath && !state.expandedPaths.has(parentPath)) {\r\n await controller.expand(parentPath)\r\n }\r\n state.creatingState = { parentPath, isDirectory, insertAfterPath }\r\n notify()\r\n },\r\n\r\n async commitCreate(name: string): Promise<void> {\r\n if (!state.creatingState) return\r\n\r\n const { parentPath, isDirectory } = state.creatingState\r\n state.creatingState = null\r\n\r\n if (isDirectory) {\r\n await controller.createDir(parentPath, name)\r\n } else {\r\n await controller.createFile(parentPath, name)\r\n }\r\n },\r\n\r\n cancelCreate(): void {\r\n state.creatingState = null\r\n notify()\r\n },\r\n\r\n async createFile(parentPath: string, name: string): Promise<void> {\r\n if (!adapter.createFile) return\r\n await adapter.createFile(parentPath, name)\r\n await refreshParent(parentPath)\r\n\r\n notify()\r\n emit({ type: 'create', parentPath, name, isDirectory: false })\r\n },\r\n\r\n async createDir(parentPath: string, name: string): Promise<void> {\r\n if (!adapter.createDir) return\r\n await adapter.createDir(parentPath, name)\r\n await refreshParent(parentPath)\r\n\r\n notify()\r\n emit({ type: 'create', parentPath, name, isDirectory: true })\r\n },\r\n\r\n async deleteSelected(): Promise<void> {\r\n if (!adapter.delete || state.selectedPaths.size === 0) return\r\n\r\n const paths = Array.from(state.selectedPaths)\r\n await adapter.delete(paths)\r\n\r\n state.selectedPaths = new Set()\r\n state.anchorPath = null\r\n\r\n // 影響を受ける親ディレクトリをリフレッシュ\r\n const parentDirs = new Set(paths.map(dirname))\r\n for (const dir of parentDirs) {\r\n await refreshParent(dir)\r\n }\r\n\r\n notify()\r\n emit({ type: 'delete', paths })\r\n },\r\n\r\n async refresh(path?: string): Promise<void> {\r\n if (!path || path === rootPath) {\r\n await refreshParent(rootPath)\r\n } else {\r\n const node = findNode(state.rootNodes, path)\r\n if (node && node.isDirectory) {\r\n await loadChildren(node)\r\n }\r\n }\r\n\r\n notify()\r\n emit({ type: 'refresh', path })\r\n },\r\n\r\n setSearchQuery(query: string | null): void {\r\n state.searchQuery = query && query.trim() ? query.trim() : null\r\n notify()\r\n },\r\n\r\n async collectAllFiles(): Promise<FileEntry[]> {\r\n const result: FileEntry[] = []\r\n\r\n async function walk(dirPath: string): Promise<void> {\r\n const entries = await adapter.readDir(dirPath)\r\n for (const entry of entries) {\r\n if (!filterFn(entry)) continue\r\n result.push(entry)\r\n if (entry.isDirectory) {\r\n await walk(entry.path)\r\n }\r\n }\r\n }\r\n\r\n await walk(rootPath)\r\n return result\r\n },\r\n\r\n setFilter(fn: ((entry: FileEntry) => boolean) | null): void {\r\n filterFn = fn ?? defaultFilter\r\n // 既に読み込み済みのノードを再フィルタ\r\n state.rootNodes = filterNodes(state.rootNodes)\r\n notify()\r\n },\r\n\r\n setSort(fn: ((a: FileEntry, b: FileEntry) => number) | null): void {\r\n sortFn = fn ?? defaultSort\r\n sortNodes(state.rootNodes)\r\n notify()\r\n },\r\n\r\n destroy(): void {\r\n stopWatching()\r\n listeners.clear()\r\n },\r\n }\r\n\r\n return controller\r\n}\r\n","import { createContext, useContext } from 'react'\r\nimport type { FileTreeController, TreeState } from '../core/types'\r\n\r\n/** TreeProvider が提供するコンテキスト値 */\r\nexport interface TreeContextValue {\r\n /** ツリー操作用コントローラ */\r\n controller: FileTreeController\r\n /** 現在のツリー状態 */\r\n state: TreeState\r\n}\r\n\r\n/** @internal ツリーコンテキスト。通常は直接使わず `useTreeContext` 経由で利用する */\r\nexport const TreeContext = createContext<TreeContextValue | null>(null)\r\n\r\n/**\r\n * TreeProvider のコンテキストを取得する。\r\n * TreeProvider の外で使用すると Error をスローする。\r\n * @returns ツリーのコントローラと状態\r\n */\r\nexport function useTreeContext(): TreeContextValue {\r\n const ctx = useContext(TreeContext)\r\n if (!ctx) {\r\n throw new Error('useTreeContext must be used within a <TreeProvider>')\r\n }\r\n return ctx\r\n}\r\n","import { useTreeContext } from './context'\r\nimport type { FileTreeController, TreeState } from '../core/types'\r\n\r\n/** {@link useFileTree} の戻り値。ツリー状態の全フィールドに加えコントローラを含む */\r\nexport interface UseFileTreeResult extends TreeState {\r\n /** ツリー操作用コントローラ */\r\n controller: FileTreeController\r\n}\r\n\r\n/**\r\n * ツリー全体の状態とコントローラを返すフック。\r\n * TreeProvider 内で使用すること。\r\n * @returns ツリー状態(flatList, expandedPaths 等)とコントローラ\r\n */\r\nexport function useFileTree(): UseFileTreeResult {\r\n const { controller, state } = useTreeContext()\r\n return { ...state, controller }\r\n}\r\n","import { useMemo } from 'react'\r\nimport { useTreeContext } from './context'\r\nimport type { TreeNode } from '../core/types'\r\n\r\n/** {@link useTreeNode} の戻り値 */\r\nexport interface UseTreeNodeResult {\r\n /** ノード本体 */\r\n node: TreeNode\r\n /** ディレクトリが展開されているか */\r\n isExpanded: boolean\r\n /** 選択されているか */\r\n isSelected: boolean\r\n /** リネーム中か */\r\n isRenaming: boolean\r\n /** ツリー上のネスト深度 */\r\n depth: number\r\n}\r\n\r\n/**\r\n * 指定パスのノード状態を返すフック。\r\n * TreeProvider 内で使用すること。パスが見つからない場合は `null` を返す。\r\n * @param path - 取得したいノードのファイルパス\r\n * @returns ノードの状態。パスが見つからなければ `null`\r\n */\r\nexport function useTreeNode(path: string): UseTreeNodeResult | null {\r\n const { state } = useTreeContext()\r\n\r\n return useMemo(() => {\r\n const flatItem = state.flatList.find((f) => f.node.path === path)\r\n if (!flatItem) return null\r\n\r\n return {\r\n node: flatItem.node,\r\n isExpanded: state.expandedPaths.has(path),\r\n isSelected: state.selectedPaths.has(path),\r\n isRenaming: state.renamingPath === path,\r\n depth: flatItem.depth,\r\n }\r\n }, [state, path])\r\n}\r\n","import { useCallback, useState } from 'react'\r\n\r\n/** コンテキストメニューの表示状態 */\r\nexport interface ContextMenuState {\r\n /** メニューが表示されているか */\r\n isVisible: boolean\r\n /** メニューのX座標(clientX) */\r\n x: number\r\n /** メニューのY座標(clientY) */\r\n y: number\r\n /** 右クリック対象のファイルパス */\r\n targetPath: string | null\r\n}\r\n\r\n/** {@link useContextMenu} の戻り値 */\r\nexport interface UseContextMenuResult extends ContextMenuState {\r\n /** 指定位置にコンテキストメニューを表示する */\r\n show(e: React.MouseEvent, targetPath: string): void\r\n /** コンテキストメニューを閉じる */\r\n hide(): void\r\n}\r\n\r\n/**\r\n * 右クリックメニューの表示位置・表示状態を管理するフック。\r\n * @returns メニュー状態と表示/非表示の操作関数\r\n */\r\nexport function useContextMenu(): UseContextMenuResult {\r\n const [menuState, setMenuState] = useState<ContextMenuState>({\r\n isVisible: false,\r\n x: 0,\r\n y: 0,\r\n targetPath: null,\r\n })\r\n\r\n const show = useCallback((e: React.MouseEvent, targetPath: string) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n setMenuState({\r\n isVisible: true,\r\n x: e.clientX,\r\n y: e.clientY,\r\n targetPath,\r\n })\r\n }, [])\r\n\r\n const hide = useCallback(() => {\r\n setMenuState((prev) => ({ ...prev, isVisible: false, targetPath: null }))\r\n }, [])\r\n\r\n return { ...menuState, show, hide }\r\n}\r\n","import { useCallback, useEffect, useRef } from 'react'\nimport { useTreeContext } from './context'\nimport { ExplorerCommands } from '../core/keybindings'\nimport type { ExplorerCommandId } from '../core/keybindings'\n\n/**\n * InputService(momoi-keybind)を受け取り、エクスプローラーのコマンドハンドラーを登録する。\n * momoi-keybindが無い場合は使わなくてOK。\n *\n * @param inputService - momoi-keybindのInputServiceインスタンス。nullならスキップ\n * @param options - コマンド実行時の追加処理\n */\nexport function useExplorerKeybindings(\n inputService: InputServiceLike | null,\n options?: {\n onCopyPath?: (paths: string[]) => void\n },\n): void {\n const { controller, state } = useTreeContext()\n const stateRef = useRef(state)\n stateRef.current = state\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const getCreateTarget = useCallback((): { parentPath: string; insertAfterPath?: string } => {\n const s = stateRef.current\n if (s.selectedPaths.size === 0) return { parentPath: s.rootPath }\n const firstSelected = s.flatList.find((f) => s.selectedPaths.has(f.node.path))\n if (!firstSelected) return { parentPath: s.rootPath }\n // フォルダ選択 → そのフォルダの子の先頭\n if (firstSelected.node.isDirectory) return { parentPath: firstSelected.node.path }\n // ファイル選択 → そのファイルの直後\n const sep = firstSelected.node.path.includes('\\\\') ? '\\\\' : '/'\n const idx = firstSelected.node.path.lastIndexOf(sep)\n const parentPath = idx === -1 ? s.rootPath : firstSelected.node.path.slice(0, idx)\n return { parentPath, insertAfterPath: firstSelected.node.path }\n }, [])\n\n useEffect(() => {\n if (!inputService) return\n\n const disposers: Array<() => void> = []\n\n const handlers: Record<ExplorerCommandId, () => void> = {\n [ExplorerCommands.DELETE]: () => controller.deleteSelected(),\n [ExplorerCommands.RENAME]: () => {\n const s = stateRef.current\n if (s.selectedPaths.size === 1) {\n controller.startRename(Array.from(s.selectedPaths)[0])\n }\n },\n [ExplorerCommands.NEW_FILE]: () => {\n const t = getCreateTarget()\n controller.startCreate(t.parentPath, false, t.insertAfterPath)\n },\n [ExplorerCommands.NEW_FOLDER]: () => {\n const t = getCreateTarget()\n controller.startCreate(t.parentPath, true, t.insertAfterPath)\n },\n [ExplorerCommands.REFRESH]: () => controller.refresh(),\n [ExplorerCommands.COLLAPSE_ALL]: () => {\n const s = stateRef.current\n for (const path of s.expandedPaths) {\n controller.collapse(path)\n }\n },\n [ExplorerCommands.SELECT_ALL]: () => controller.selectAll(),\n [ExplorerCommands.COPY_PATH]: () => {\n const s = stateRef.current\n const paths = Array.from(s.selectedPaths)\n optionsRef.current?.onCopyPath?.(paths)\n },\n }\n\n for (const [command, handler] of Object.entries(handlers)) {\n disposers.push(inputService.registerCommand(command, handler))\n }\n\n return () => {\n for (const dispose of disposers) {\n dispose()\n }\n }\n }, [inputService, controller, getCreateTarget])\n}\n\n/**\n * momoi-keybindのInputServiceの最小インターフェース。\n * momoi-keybindに直接依存せず、duck typingで受け入れる。\n */\nexport interface InputServiceLike {\n registerCommand(command: string, handler: (args?: unknown) => void): () => void\n setContext(key: string, value: unknown): void\n deleteContext(key: string): void\n}\n","// エクスプローラー用のコマンドIDとデフォルトキーバインド定義\n//\n// momoi-keybind の KeybindingEntry 互換の形式でエクスポートする。\n// momoi-keybind が無くても型として参照できるように、独自の型を定義。\n\n/** momoi-keybind の KeybindingEntry と互換の型 */\nexport interface ExplorerKeybindingEntry {\n key: string\n command: string\n when?: string\n args?: unknown\n}\n\n/** エクスプローラーのコマンドID */\nexport const ExplorerCommands = {\n DELETE: 'explorer.delete',\n RENAME: 'explorer.rename',\n NEW_FILE: 'explorer.newFile',\n NEW_FOLDER: 'explorer.newFolder',\n REFRESH: 'explorer.refresh',\n COLLAPSE_ALL: 'explorer.collapseAll',\n SELECT_ALL: 'explorer.selectAll',\n COPY_PATH: 'explorer.copyPath',\n} as const\n\nexport type ExplorerCommandId = (typeof ExplorerCommands)[keyof typeof ExplorerCommands]\n\n/** デフォルトのキーバインド定義。momoi-keybindのInputServiceに渡せる形式 */\nexport const defaultExplorerKeybindings: ExplorerKeybindingEntry[] = [\n { key: 'Delete', command: ExplorerCommands.DELETE, when: 'explorerFocus' },\n { key: 'F2', command: ExplorerCommands.RENAME, when: 'explorerFocus' },\n { key: 'Ctrl+N', command: ExplorerCommands.NEW_FILE, when: 'explorerFocus' },\n { key: 'Ctrl+Shift+N', command: ExplorerCommands.NEW_FOLDER, when: 'explorerFocus' },\n { key: 'Ctrl+R', command: ExplorerCommands.REFRESH, when: 'explorerFocus' },\n { key: 'Ctrl+Shift+E', command: ExplorerCommands.COLLAPSE_ALL, when: 'explorerFocus' },\n { key: 'Ctrl+A', command: ExplorerCommands.SELECT_ALL, when: 'explorerFocus' },\n { key: 'Ctrl+Shift+C', command: ExplorerCommands.COPY_PATH, when: 'explorerFocus' },\n]\n","import { useCallback, useRef } from 'react'\nimport type { InputServiceLike } from './useExplorerKeybindings'\n\n/**\n * エクスプローラーのフォーカス状態をmomoi-keybindのコンテキストに連動させるhook。\n * 返されたpropsをエクスプローラーのルート要素に渡す。\n *\n * @param inputService - momoi-keybindのInputServiceインスタンス。nullならno-op\n * @param contextKey - コンテキストキー名。デフォルト: 'explorerFocus'\n */\nexport function useExplorerFocus(\n inputService: InputServiceLike | null,\n contextKey: string = 'explorerFocus',\n): {\n onFocus: () => void\n onBlur: () => void\n tabIndex: number\n} {\n const focused = useRef(false)\n\n const onFocus = useCallback(() => {\n if (!focused.current) {\n focused.current = true\n inputService?.setContext(contextKey, true)\n }\n }, [inputService, contextKey])\n\n const onBlur = useCallback(() => {\n if (focused.current) {\n focused.current = false\n inputService?.deleteContext(contextKey)\n }\n }, [inputService, contextKey])\n\n return { onFocus, onBlur, tabIndex: 0 }\n}\n"],"mappings":"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","names":["useMemo","useMemo","useState","useCallback","useEffect","useRef","useRef","useCallback","useEffect","useCallback","useRef"]}
@@ -0,0 +1,332 @@
1
+ /* momoi-explorer default dark theme */
2
+
3
+ .momoi-explorer {
4
+ --me-bg: #1e1e1e;
5
+ --me-fg: #cccccc;
6
+ --me-fg-muted: #888888;
7
+ --me-border: #333333;
8
+ --me-hover-bg: #2a2d2e;
9
+ --me-selected-bg: #094771;
10
+ --me-selected-fg: #ffffff;
11
+ --me-focus-border: #007fd4;
12
+ --me-indent-guide: #404040;
13
+ --me-scrollbar-bg: #1e1e1e;
14
+ --me-scrollbar-thumb: #424242;
15
+ --me-ctx-bg: #252526;
16
+ --me-ctx-hover-bg: #094771;
17
+ --me-ctx-border: #454545;
18
+ --me-ctx-fg: #cccccc;
19
+ --me-ctx-shortcut-fg: #888888;
20
+ --me-ctx-separator: #454545;
21
+ --me-rename-bg: #1e1e1e;
22
+ --me-rename-border: #007fd4;
23
+ --me-row-height: 22px;
24
+ --me-indent-size: 16px;
25
+ --me-icon-size: 16px;
26
+ --me-font-size: 13px;
27
+ --me-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
28
+
29
+ position: relative;
30
+ width: 100%;
31
+ height: 100%;
32
+ overflow: hidden;
33
+ background: var(--me-bg);
34
+ color: var(--me-fg);
35
+ font-family: var(--me-font-family);
36
+ font-size: var(--me-font-size);
37
+ user-select: none;
38
+ }
39
+
40
+ /* ツリー行 */
41
+ .momoi-explorer-row {
42
+ display: flex;
43
+ align-items: center;
44
+ height: var(--me-row-height);
45
+ padding-right: 8px;
46
+ cursor: pointer;
47
+ white-space: nowrap;
48
+ }
49
+
50
+ .momoi-explorer-row:hover {
51
+ background: var(--me-hover-bg);
52
+ }
53
+
54
+ .momoi-explorer-row[data-selected='true'] {
55
+ background: var(--me-selected-bg);
56
+ color: var(--me-selected-fg);
57
+ }
58
+
59
+ /* インデント */
60
+ .momoi-explorer-indent {
61
+ display: inline-flex;
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ .momoi-explorer-indent-guide {
66
+ width: var(--me-indent-size);
67
+ height: var(--me-row-height);
68
+ border-right: 1px solid var(--me-indent-guide);
69
+ box-sizing: border-box;
70
+ }
71
+
72
+ /* 展開アイコン */
73
+ .momoi-explorer-chevron {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ width: var(--me-indent-size);
78
+ height: var(--me-row-height);
79
+ flex-shrink: 0;
80
+ }
81
+
82
+ .momoi-explorer-chevron svg {
83
+ width: 10px;
84
+ height: 10px;
85
+ fill: var(--me-fg-muted);
86
+ transition: transform 0.1s ease;
87
+ }
88
+
89
+ .momoi-explorer-chevron[data-expanded='true'] svg {
90
+ transform: rotate(90deg);
91
+ }
92
+
93
+ .momoi-explorer-chevron[data-is-dir='false'] {
94
+ visibility: hidden;
95
+ }
96
+
97
+ /* ファイルアイコン */
98
+ .momoi-explorer-icon {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ width: var(--me-icon-size);
103
+ height: var(--me-icon-size);
104
+ margin-right: 4px;
105
+ flex-shrink: 0;
106
+ }
107
+
108
+ .momoi-explorer-icon svg {
109
+ width: var(--me-icon-size);
110
+ height: var(--me-icon-size);
111
+ }
112
+
113
+ /* material-file-icons のSVGラッパー */
114
+ .momoi-explorer-icon-inner {
115
+ display: inline-flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ width: var(--me-icon-size);
119
+ height: var(--me-icon-size);
120
+ }
121
+
122
+ .momoi-explorer-icon-inner svg {
123
+ width: var(--me-icon-size);
124
+ height: var(--me-icon-size);
125
+ }
126
+
127
+ /* ファイル名 */
128
+ .momoi-explorer-name {
129
+ flex: 1;
130
+ overflow: hidden;
131
+ text-overflow: ellipsis;
132
+ line-height: var(--me-row-height);
133
+ }
134
+
135
+ /* バッジ */
136
+ .momoi-explorer-badge {
137
+ margin-left: auto;
138
+ padding-left: 8px;
139
+ flex-shrink: 0;
140
+ }
141
+
142
+ /* インラインリネーム */
143
+ .momoi-explorer-rename-input {
144
+ flex: 1;
145
+ height: calc(var(--me-row-height) - 4px);
146
+ background: var(--me-rename-bg);
147
+ color: var(--me-fg);
148
+ border: 1px solid var(--me-rename-border);
149
+ font-family: var(--me-font-family);
150
+ font-size: var(--me-font-size);
151
+ padding: 0 4px;
152
+ outline: none;
153
+ box-sizing: border-box;
154
+ }
155
+
156
+ /* コンテキストメニュー */
157
+ .momoi-explorer-context-menu {
158
+ position: fixed;
159
+ z-index: 9999;
160
+ min-width: 160px;
161
+ background: var(--me-ctx-bg);
162
+ border: 1px solid var(--me-ctx-border);
163
+ border-radius: 4px;
164
+ padding: 4px 0;
165
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
166
+ }
167
+
168
+ .momoi-explorer-context-menu-item {
169
+ display: flex;
170
+ align-items: center;
171
+ height: 28px;
172
+ padding: 0 24px 0 12px;
173
+ cursor: pointer;
174
+ color: var(--me-ctx-fg);
175
+ gap: 16px;
176
+ }
177
+
178
+ .momoi-explorer-context-menu-item:hover {
179
+ background: var(--me-ctx-hover-bg);
180
+ color: var(--me-selected-fg);
181
+ }
182
+
183
+ .momoi-explorer-context-menu-item[data-disabled='true'] {
184
+ opacity: 0.5;
185
+ pointer-events: none;
186
+ }
187
+
188
+ .momoi-explorer-context-menu-shortcut {
189
+ margin-left: auto;
190
+ color: var(--me-ctx-shortcut-fg);
191
+ font-size: 12px;
192
+ }
193
+
194
+ .momoi-explorer-context-menu-separator {
195
+ height: 1px;
196
+ margin: 4px 0;
197
+ background: var(--me-ctx-separator);
198
+ }
199
+
200
+ /* ツリーフィルタバー */
201
+ .momoi-explorer-filter-bar {
202
+ display: flex;
203
+ align-items: center;
204
+ padding: 4px 8px;
205
+ border-bottom: 1px solid var(--me-border);
206
+ position: relative;
207
+ }
208
+
209
+ .momoi-explorer-filter-input {
210
+ width: 100%;
211
+ height: 24px;
212
+ background: var(--me-rename-bg);
213
+ color: var(--me-fg);
214
+ border: 1px solid var(--me-border);
215
+ border-radius: 3px;
216
+ font-family: var(--me-font-family);
217
+ font-size: var(--me-font-size);
218
+ padding: 0 22px 0 6px;
219
+ outline: none;
220
+ box-sizing: border-box;
221
+ }
222
+
223
+ .momoi-explorer-filter-input:focus {
224
+ border-color: var(--me-focus-border);
225
+ }
226
+
227
+ .momoi-explorer-filter-clear {
228
+ position: absolute;
229
+ right: 14px;
230
+ cursor: pointer;
231
+ color: var(--me-fg-muted);
232
+ font-size: 14px;
233
+ line-height: 1;
234
+ }
235
+
236
+ .momoi-explorer-filter-clear:hover {
237
+ color: var(--me-fg);
238
+ }
239
+
240
+ /* QuickOpen オーバーレイ */
241
+ .momoi-explorer-quickopen-overlay {
242
+ position: fixed;
243
+ inset: 0;
244
+ z-index: 10000;
245
+ display: flex;
246
+ justify-content: center;
247
+ padding-top: 15vh;
248
+ background: rgba(0, 0, 0, 0.3);
249
+ }
250
+
251
+ .momoi-explorer-quickopen {
252
+ width: 500px;
253
+ max-width: 90vw;
254
+ max-height: 400px;
255
+ background: var(--me-ctx-bg);
256
+ border: 1px solid var(--me-ctx-border);
257
+ border-radius: 6px;
258
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
259
+ display: flex;
260
+ flex-direction: column;
261
+ overflow: hidden;
262
+ align-self: flex-start;
263
+ }
264
+
265
+ .momoi-explorer-quickopen-input {
266
+ width: 100%;
267
+ height: 36px;
268
+ background: transparent;
269
+ color: var(--me-fg);
270
+ border: none;
271
+ border-bottom: 1px solid var(--me-border);
272
+ font-family: var(--me-font-family);
273
+ font-size: 14px;
274
+ padding: 0 12px;
275
+ outline: none;
276
+ box-sizing: border-box;
277
+ }
278
+
279
+ .momoi-explorer-quickopen-results {
280
+ overflow-y: auto;
281
+ max-height: 360px;
282
+ }
283
+
284
+ .momoi-explorer-quickopen-item {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: 12px;
288
+ height: 28px;
289
+ padding: 0 12px;
290
+ cursor: pointer;
291
+ overflow: hidden;
292
+ }
293
+
294
+ .momoi-explorer-quickopen-item:hover,
295
+ .momoi-explorer-quickopen-item[data-selected='true'] {
296
+ background: var(--me-selected-bg);
297
+ }
298
+
299
+ .momoi-explorer-quickopen-icon {
300
+ display: inline-flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ width: 16px;
304
+ height: 16px;
305
+ flex-shrink: 0;
306
+ }
307
+
308
+ .momoi-explorer-quickopen-icon svg {
309
+ width: 16px;
310
+ height: 16px;
311
+ }
312
+
313
+ .momoi-explorer-quickopen-name {
314
+ color: var(--me-fg);
315
+ white-space: nowrap;
316
+ flex-shrink: 0;
317
+ }
318
+
319
+ .momoi-explorer-quickopen-path {
320
+ color: var(--me-fg-muted);
321
+ font-size: 12px;
322
+ overflow: hidden;
323
+ text-overflow: ellipsis;
324
+ white-space: nowrap;
325
+ }
326
+
327
+ .momoi-explorer-quickopen-empty {
328
+ padding: 12px;
329
+ color: var(--me-fg-muted);
330
+ text-align: center;
331
+ font-style: italic;
332
+ }