@aiquants/directory-tree 3.0.4 → 3.1.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/README.md +25 -8
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/styles/directory-tree.css +1 -1
- package/dist/styles/directory-tree.standalone.css +3 -0
- package/package.json +10 -8
- package/src/DirectoryTree.spec.md +473 -0
- package/src/DirectoryTree.tsx +978 -0
- package/src/DirectoryTreeGrid.tsx +261 -0
- package/src/TreeLine.tsx +300 -0
- package/src/cli.server.ts +38 -0
- package/src/entryRendering.tsx +131 -0
- package/src/gridUtils.ts +88 -0
- package/src/index.ts +31 -0
- package/src/logger.ts +162 -0
- package/src/styles/components.entry.css +8 -0
- package/src/styles/directory-tree.css +89 -0
- package/src/styles/standalone.entry.css +12 -0
- package/src/types.ts +619 -0
- package/src/useDirectoryTreeState.ts +133 -0
- package/src/useEntryInteraction.ts +74 -0
- package/dist/directory-tree.css +0 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module entryRendering
|
|
3
|
+
* @description Shared constants, class names and icon-resolution helpers used by both the classic
|
|
4
|
+
* label row (DirectoryEntryItem) and the TreeGrid row (DirectoryTreeGridRow).
|
|
5
|
+
* Extracted from DirectoryTree.tsx so the two row renderers can share them without a
|
|
6
|
+
* circular import.
|
|
7
|
+
* 従来行と TreeGrid 行が共有する定数・クラス・アイコン解決ヘルパ。循環 import 回避のため分離。
|
|
8
|
+
*/
|
|
9
|
+
import { ChevronDownIcon, ChevronRightIcon, DocumentTextIcon, FolderIcon } from "@heroicons/react/24/solid"
|
|
10
|
+
import { FileIcon } from "@phosphor-icons/react"
|
|
11
|
+
import type { ReactNode } from "react"
|
|
12
|
+
import { twMerge } from "tailwind-merge"
|
|
13
|
+
import type { DirectoryTreeIconOverrides, DirectoryTreeIconRenderContext, DirectoryTreeIconRenderer } from "./types.ts"
|
|
14
|
+
|
|
15
|
+
/** Default row height in px. / 各行の高さの既定値 (px)。 */
|
|
16
|
+
export const DEFAULT_ITEM_HEIGHT = 20
|
|
17
|
+
/** Indentation per depth level in px. / 1 階層あたりのインデント幅 (px)。 */
|
|
18
|
+
export const INDENT_SIZE = 16
|
|
19
|
+
/** Standard expand glyph size. / 展開アイコンの標準サイズ。 */
|
|
20
|
+
export const EXPAND_GLYPH_SIZE = 12
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* TailwindCSS v4 class names for DirectoryTree component.
|
|
24
|
+
* DirectoryTreeコンポーネント用の TailwindCSS v4 クラス名。
|
|
25
|
+
*/
|
|
26
|
+
export const directoryTreeClasses = {
|
|
27
|
+
container: "directory-tree-container relative h-full transition-opacity duration-300 overflow-y-hidden pb-[5px] pr-[3px]",
|
|
28
|
+
containerPending: "opacity-70",
|
|
29
|
+
|
|
30
|
+
entry: "directory-tree-entry flex items-center cursor-pointer relative select-none",
|
|
31
|
+
|
|
32
|
+
// ホバー時のスタイル(ライト/ダークモード対応)
|
|
33
|
+
entryHover: twMerge("directory-tree-entry--hover hover:bg-gray-400/15", "dark:hover:bg-gray-200/10"),
|
|
34
|
+
|
|
35
|
+
// 選択時のスタイル(ライト/ダークモード対応)
|
|
36
|
+
entrySelected: twMerge("directory-tree-entry--selected bg-blue-400/10", "dark:bg-blue-400/15"),
|
|
37
|
+
|
|
38
|
+
// アイテム選択時のスタイル(ライト/ダークモード対応)
|
|
39
|
+
entryItemSelected: twMerge("directory-tree-entry--item-selected bg-blue-400/20 shadow-[0_0_0_1px_rgba(59,130,246,0.3)]", "dark:bg-blue-400/25 dark:shadow-[0_0_0_1px_rgba(96,165,250,0.4)]"),
|
|
40
|
+
|
|
41
|
+
// アイコンのスタイル
|
|
42
|
+
expandIcon: twMerge("directory-tree-expand-icon w-5 h-5 flex-shrink-0 flex items-center justify-center", "text-gray-500", "dark:text-gray-400"),
|
|
43
|
+
|
|
44
|
+
expandIconSelected: twMerge("directory-tree-expand-icon--selected text-blue-700", "dark:text-blue-400"),
|
|
45
|
+
|
|
46
|
+
typeIcon: twMerge("directory-tree-type-icon w-5 h-5 flex-shrink-0 flex items-center justify-center", "text-gray-500", "dark:text-gray-400"),
|
|
47
|
+
|
|
48
|
+
typeIconSelected: twMerge("directory-tree-type-icon--selected text-blue-700", "dark:text-blue-400"),
|
|
49
|
+
|
|
50
|
+
// テキストのスタイル
|
|
51
|
+
name: twMerge("directory-tree-name overflow-hidden text-ellipsis whitespace-nowrap ml-1", "text-gray-700", "dark:text-gray-200"),
|
|
52
|
+
|
|
53
|
+
nameDirectory: "directory-tree-name--directory",
|
|
54
|
+
|
|
55
|
+
nameSelected: twMerge("directory-tree-name--selected text-blue-800 font-medium", "dark:text-blue-300"),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @function getFileExtension
|
|
60
|
+
* @description Extracts the extension from a filename. / ファイル名から拡張子を抽出する。
|
|
61
|
+
*/
|
|
62
|
+
export const getFileExtension = (filename: string): string => {
|
|
63
|
+
return filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2).toLowerCase()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ExpandIconParams = {
|
|
67
|
+
isDirectory: boolean
|
|
68
|
+
isExpanded: boolean
|
|
69
|
+
showExpandIcons: boolean
|
|
70
|
+
useCanvasExpandIcons: boolean
|
|
71
|
+
expandIconSize?: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @function resolveExpandIcon
|
|
76
|
+
* @description Determines the expand icon node for a directory entry. / 展開アイコンノードを決定する。
|
|
77
|
+
*/
|
|
78
|
+
export const resolveExpandIcon = ({ isDirectory, isExpanded, showExpandIcons, useCanvasExpandIcons, expandIconSize }: ExpandIconParams): ReactNode => {
|
|
79
|
+
const style = expandIconSize ? { width: expandIconSize, height: expandIconSize } : undefined
|
|
80
|
+
const className = expandIconSize ? "" : "h-4 w-4"
|
|
81
|
+
|
|
82
|
+
if (!(showExpandIcons && isDirectory) || useCanvasExpandIcons) {
|
|
83
|
+
return <span className={className} style={style} aria-hidden="true" />
|
|
84
|
+
}
|
|
85
|
+
return isExpanded ? <ChevronDownIcon className={className} style={style} /> : <ChevronRightIcon className={className} style={style} />
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves a directory tree icon renderer into a ReactNode.
|
|
90
|
+
* ディレクトリツリーのアイコン描画設定を ReactNode に解決する関数。
|
|
91
|
+
*/
|
|
92
|
+
const sanitizeIcon = (node: unknown, path: string): ReactNode | null => {
|
|
93
|
+
if (node === null || node === undefined || typeof node === "boolean") return null
|
|
94
|
+
if (typeof node === "function") {
|
|
95
|
+
console.warn(`[DirectoryTree] Function returned by renderer for "${path}". Return a node.`)
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
if (typeof node === "object") {
|
|
99
|
+
if ("then" in node) {
|
|
100
|
+
console.warn(`[DirectoryTree] Promise returned for "${path}". Not supported.`)
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(node)) {
|
|
104
|
+
const kids = node.map((c) => sanitizeIcon(c, path)).filter(Boolean)
|
|
105
|
+
return kids.length ? kids : null
|
|
106
|
+
}
|
|
107
|
+
const type = (node as { type?: { $$typeof?: symbol } }).type
|
|
108
|
+
if (type && type.$$typeof === Symbol.for("react.lazy")) {
|
|
109
|
+
console.warn(`[DirectoryTree] Lazy component for "${path}". Not supported.`)
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return node as ReactNode
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Determines the icon to render for a directory entry.
|
|
118
|
+
* ディレクトリエントリに表示するアイコンを決定する関数。
|
|
119
|
+
*/
|
|
120
|
+
export const resolveEntryIcon = (ctx: DirectoryTreeIconRenderContext, overrides?: DirectoryTreeIconOverrides): ReactNode => {
|
|
121
|
+
const { entry, isDirectory, isExpanded, extension } = ctx
|
|
122
|
+
let target: ReactNode | DirectoryTreeIconRenderer | undefined
|
|
123
|
+
|
|
124
|
+
if (entry.icon !== undefined) target = entry.icon
|
|
125
|
+
else if (isDirectory) target = (isExpanded && overrides?.directoryExpanded) || overrides?.directory || <FolderIcon className="h-4 w-4" />
|
|
126
|
+
else if (extension && overrides?.fileByExtension?.[extension]) target = overrides.fileByExtension[extension]
|
|
127
|
+
else target = overrides?.file || (extension === "md" ? <DocumentTextIcon className="h-4 w-4" /> : <FileIcon className="h-4 w-4" />)
|
|
128
|
+
|
|
129
|
+
if (typeof target === "function") return sanitizeIcon(target(ctx), entry.absolutePath)
|
|
130
|
+
return sanitizeIcon(target, entry.absolutePath)
|
|
131
|
+
}
|
package/src/gridUtils.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module gridUtils
|
|
3
|
+
* @description Pure helpers for the DirectoryTree TreeGrid (columns) mode. Kept free of DOM/React
|
|
4
|
+
* so the layout math can be unit-tested in isolation.
|
|
5
|
+
* TreeGrid(列)モード用の純粋ヘルパ。DOM/React 非依存で単体テスト可能に保つ。
|
|
6
|
+
*/
|
|
7
|
+
import type { DirectoryEntry, DirectoryTreeColumn } from "./types.ts"
|
|
8
|
+
|
|
9
|
+
/** Default frozen width of the name (tree) column in px. / 名前列の既定凍結幅 (px)。 */
|
|
10
|
+
export const DEFAULT_NAME_COLUMN_WIDTH = 240
|
|
11
|
+
/** Default width of a numeric column in px. / 数値列の既定幅 (px)。 */
|
|
12
|
+
export const DEFAULT_NUMERIC_COLUMN_WIDTH = 96
|
|
13
|
+
/** Default vertical scrollbar width used to align header/footer with the body. / ヘッダ/フッタ整合用の既定縦バー幅。 */
|
|
14
|
+
export const DEFAULT_GRID_SCROLLBAR_WIDTH = 12
|
|
15
|
+
|
|
16
|
+
/** Resolves the effective width of a column, applying defaults. / 列の実効幅を既定込みで解決する。 */
|
|
17
|
+
const resolveColumnWidth = (column: DirectoryTreeColumn | undefined, fallback: number): number => {
|
|
18
|
+
const width = column?.width
|
|
19
|
+
return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : fallback
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds the `grid-template-columns` string for the numeric (non-name) columns.
|
|
24
|
+
* 数値列(columns[1..])の `grid-template-columns` 文字列を構築する。
|
|
25
|
+
*
|
|
26
|
+
* @returns e.g. `"96px 96px 120px"`. Empty string when there is no numeric column.
|
|
27
|
+
*/
|
|
28
|
+
export const buildNumericTemplate = (columns: DirectoryTreeColumn[]): string =>
|
|
29
|
+
columns
|
|
30
|
+
.slice(1)
|
|
31
|
+
.map((column) => `${resolveColumnWidth(column, DEFAULT_NUMERIC_COLUMN_WIDTH)}px`)
|
|
32
|
+
.join(" ")
|
|
33
|
+
|
|
34
|
+
/** Metrics describing the frozen/scrollable split of a TreeGrid. / TreeGrid の凍結/可動分割を表す寸法。 */
|
|
35
|
+
export type GridMetrics = {
|
|
36
|
+
/** Frozen width of the name column. / 名前列の凍結幅。 */
|
|
37
|
+
nameWidth: number
|
|
38
|
+
/** Total width of all numeric columns (the scrollable content width). / 数値列合計幅(可動コンテンツ幅)。 */
|
|
39
|
+
numericTotal: number
|
|
40
|
+
/** Visible width of the numeric region (viewport). / 数値領域の可視幅(ビューポート)。 */
|
|
41
|
+
numericVisible: number
|
|
42
|
+
/** Maximum horizontal scroll offset (>= 0). / 横スクロールの最大オフセット (>= 0)。 */
|
|
43
|
+
maxHScroll: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Computes the frozen/scrollable layout metrics from the measured container width.
|
|
48
|
+
* 計測済みコンテナ幅から凍結/可動レイアウトの寸法を算出する。
|
|
49
|
+
*
|
|
50
|
+
* `numericVisible = measuredWidth - nameWidth - scrollBarWidth`, clamped to >= 0. This single
|
|
51
|
+
* source of truth keeps the body (reduced by the vertical scrollbar) aligned with the
|
|
52
|
+
* header/footer (which reserve an equal right-side spacer).
|
|
53
|
+
*/
|
|
54
|
+
export const computeGridMetrics = ({ measuredWidth, columns, scrollBarWidth = DEFAULT_GRID_SCROLLBAR_WIDTH }: { measuredWidth: number; columns: DirectoryTreeColumn[]; scrollBarWidth?: number }): GridMetrics => {
|
|
55
|
+
const nameWidth = resolveColumnWidth(columns[0], DEFAULT_NAME_COLUMN_WIDTH)
|
|
56
|
+
const numericTotal = columns.slice(1).reduce((sum, column) => sum + resolveColumnWidth(column, DEFAULT_NUMERIC_COLUMN_WIDTH), 0)
|
|
57
|
+
const safeWidth = Number.isFinite(measuredWidth) && measuredWidth > 0 ? measuredWidth : 0
|
|
58
|
+
const numericVisible = Math.max(0, safeWidth - nameWidth - Math.max(0, scrollBarWidth))
|
|
59
|
+
const maxHScroll = Math.max(0, numericTotal - numericVisible)
|
|
60
|
+
return { nameWidth, numericTotal, numericVisible, maxHScroll }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Clamps a horizontal scroll offset into `[0, maxHScroll]`. / 横スクロール量を [0, maxHScroll] に丸める。 */
|
|
64
|
+
export const clampHScroll = (value: number, maxHScroll: number): number => {
|
|
65
|
+
if (!Number.isFinite(value)) return 0
|
|
66
|
+
if (value < 0) return 0
|
|
67
|
+
if (value > maxHScroll) return maxHScroll
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Depth-first flattens every node of the tree **regardless of expansion state** — used to feed
|
|
73
|
+
* column footers that total over all entries (including collapsed subtrees).
|
|
74
|
+
* 展開状態に依存せず全ノードを DFS で平坦化する。列フッタの全 entries 合計に使用。
|
|
75
|
+
*/
|
|
76
|
+
export const flattenAllEntries = (entries: DirectoryEntry[]): DirectoryEntry[] => {
|
|
77
|
+
const result: DirectoryEntry[] = []
|
|
78
|
+
const walk = (nodes: DirectoryEntry[]): void => {
|
|
79
|
+
for (const node of nodes) {
|
|
80
|
+
result.push(node)
|
|
81
|
+
if (node.children && node.children.length > 0) {
|
|
82
|
+
walk(node.children)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
walk(entries)
|
|
87
|
+
return result
|
|
88
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Directory Tree Component Library
|
|
3
|
+
* @package @aiquants/directory-tree
|
|
4
|
+
*
|
|
5
|
+
* This module provides a comprehensive directory tree component for React applications.
|
|
6
|
+
* The component supports virtualization for performance, file selection, and theming.
|
|
7
|
+
*
|
|
8
|
+
* このモジュールは React アプリケーション用の包括的なディレクトリツリーコンポーネントを提供します。
|
|
9
|
+
* このコンポーネントはパフォーマンス向上のための仮想化、ファイル選択、およびテーマ設定をサポートしています。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export { DirectoryTree, directoryTreeClasses } from "./DirectoryTree.tsx"
|
|
13
|
+
export type {
|
|
14
|
+
DirectoryEntry,
|
|
15
|
+
DirectoryEntryItemProps,
|
|
16
|
+
DirectoryTreeClickEvent,
|
|
17
|
+
DirectoryTreeColumn,
|
|
18
|
+
DirectoryTreeGrid,
|
|
19
|
+
DirectoryTreeIconOverrides,
|
|
20
|
+
DirectoryTreeIconRenderContext,
|
|
21
|
+
DirectoryTreeIconRenderer,
|
|
22
|
+
DirectoryTreeProps,
|
|
23
|
+
DirectoryTreeState,
|
|
24
|
+
DirectoryTreeVirtualScrollOptions,
|
|
25
|
+
HighlightStyles,
|
|
26
|
+
ItemHeight,
|
|
27
|
+
TreeLineItemInfo,
|
|
28
|
+
TreeLineProps,
|
|
29
|
+
UseDirectoryTreeStateProps,
|
|
30
|
+
} from "./types.ts"
|
|
31
|
+
export { useDirectoryTreeState } from "./useDirectoryTreeState.ts"
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils/logger
|
|
3
|
+
* @description Logger utility for consistent log management across the library.
|
|
4
|
+
* @description ライブラリ全体で一貫したログ管理を行うためのロガーユーティリティ。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @enum LogLevel
|
|
9
|
+
* @description Log levels for filtering output.
|
|
10
|
+
* @description 出力をフィルタリングするためのログレベル。
|
|
11
|
+
*/
|
|
12
|
+
export enum LogLevel {
|
|
13
|
+
DEBUG = 0,
|
|
14
|
+
INFO = 1,
|
|
15
|
+
WARN = 2,
|
|
16
|
+
ERROR = 3,
|
|
17
|
+
NONE = 4,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @interface ILogger
|
|
22
|
+
* @description Interface for a logger object compatible with Console.
|
|
23
|
+
* @description Console と互換性のあるロガーオブジェクトのインターフェース。
|
|
24
|
+
*/
|
|
25
|
+
export interface ILogger {
|
|
26
|
+
debug(message?: unknown, ...optionalParams: unknown[]): void
|
|
27
|
+
info(message?: unknown, ...optionalParams: unknown[]): void
|
|
28
|
+
warn(message?: unknown, ...optionalParams: unknown[]): void
|
|
29
|
+
error(message?: unknown, ...optionalParams: unknown[]): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @class Logger
|
|
34
|
+
* @description A wrapper class for handling logging with levels and prefixes.
|
|
35
|
+
* @description レベルとプレフィックスを使用したログ記録を処理するためのラッパークラス。
|
|
36
|
+
*/
|
|
37
|
+
export class Logger implements ILogger {
|
|
38
|
+
private level: LogLevel
|
|
39
|
+
private prefix: string
|
|
40
|
+
private impl: ILogger
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @constructor
|
|
44
|
+
* @param {LogLevel} [level=LogLevel.WARN] - The minimum log level to output.
|
|
45
|
+
* @param {string} [prefix="[fuzzy-search]"] - The prefix to add to all log messages.
|
|
46
|
+
* @param {ILogger} [impl=console] - The implementation to use for logging.
|
|
47
|
+
*/
|
|
48
|
+
constructor(level: LogLevel = LogLevel.WARN, prefix: string = "[fuzzy-search]", impl: ILogger = console) {
|
|
49
|
+
this.level = level
|
|
50
|
+
this.prefix = prefix
|
|
51
|
+
this.impl = impl
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static instance: Logger = new Logger(LogLevel.WARN, "[fuzzy-search]")
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @method setLevel
|
|
58
|
+
* @description Updates the current log level for the static instance.
|
|
59
|
+
* @description 静的インスタンスの現在のログレベルを更新します。
|
|
60
|
+
* @param {LogLevel} level - The new log level.
|
|
61
|
+
*/
|
|
62
|
+
static setLevel(level: LogLevel): void {
|
|
63
|
+
Logger.instance.setLevel(level)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @method setLevel
|
|
68
|
+
* @description Updates the current log level.
|
|
69
|
+
* @description 現在のログレベルを更新します。
|
|
70
|
+
* @param {LogLevel} level - The new log level.
|
|
71
|
+
*/
|
|
72
|
+
setLevel(level: LogLevel): void {
|
|
73
|
+
this.level = level
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @method setImplementation
|
|
78
|
+
* @description Updates the logger implementation for the static instance.
|
|
79
|
+
* @description 静的インスタンスのロガーの実装を更新します。
|
|
80
|
+
* @param {ILogger} impl - The new logger implementation.
|
|
81
|
+
*/
|
|
82
|
+
static setImplementation(impl: ILogger): void {
|
|
83
|
+
Logger.instance.setImplementation(impl)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @method setImplementation
|
|
88
|
+
* @description Updates the logger implementation.
|
|
89
|
+
* @description ロガーの実装を更新します。
|
|
90
|
+
* @param {ILogger} impl - The new logger implementation.
|
|
91
|
+
*/
|
|
92
|
+
setImplementation(impl: ILogger): void {
|
|
93
|
+
this.impl = impl
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @method setPrefix
|
|
98
|
+
* @description Updates the log prefix for the static instance.
|
|
99
|
+
* @description 静的インスタンスのログのプレフィックスを更新します。
|
|
100
|
+
* @param {string} prefix - The new prefix.
|
|
101
|
+
*/
|
|
102
|
+
static setPrefix(prefix: string): void {
|
|
103
|
+
Logger.instance.setPrefix(prefix)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @method setPrefix
|
|
108
|
+
* @description Updates the log prefix.
|
|
109
|
+
* @description ログのプレフィックスを更新します。
|
|
110
|
+
* @param {string} prefix - The new prefix.
|
|
111
|
+
*/
|
|
112
|
+
setPrefix(prefix: string): void {
|
|
113
|
+
this.prefix = prefix
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private formatMessage(message: unknown): unknown[] {
|
|
117
|
+
if (typeof message === "string") {
|
|
118
|
+
return [`${this.prefix} ${message}`]
|
|
119
|
+
}
|
|
120
|
+
return [this.prefix, message]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static debug(message?: unknown, ...optionalParams: unknown[]): void {
|
|
124
|
+
Logger.instance.debug(message, ...optionalParams)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
debug(message?: unknown, ...optionalParams: unknown[]): void {
|
|
128
|
+
if (this.level <= LogLevel.DEBUG) {
|
|
129
|
+
this.impl.debug(...this.formatMessage(message), ...optionalParams)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static info(message?: unknown, ...optionalParams: unknown[]): void {
|
|
134
|
+
Logger.instance.info(message, ...optionalParams)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
info(message?: unknown, ...optionalParams: unknown[]): void {
|
|
138
|
+
if (this.level <= LogLevel.INFO) {
|
|
139
|
+
this.impl.info(...this.formatMessage(message), ...optionalParams)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static warn(message?: unknown, ...optionalParams: unknown[]): void {
|
|
144
|
+
Logger.instance.warn(message, ...optionalParams)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
warn(message?: unknown, ...optionalParams: unknown[]): void {
|
|
148
|
+
if (this.level <= LogLevel.WARN) {
|
|
149
|
+
this.impl.warn(...this.formatMessage(message), ...optionalParams)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static error(message?: unknown, ...optionalParams: unknown[]): void {
|
|
154
|
+
Logger.instance.error(message, ...optionalParams)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
error(message?: unknown, ...optionalParams: unknown[]): void {
|
|
158
|
+
if (this.level <= LogLevel.ERROR) {
|
|
159
|
+
this.impl.error(...this.formatMessage(message), ...optionalParams)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Artifact A: components-only CSS (Tailwind ホスト向け).
|
|
3
|
+
* 手書き .dt-* コンポーネントクラスのみ。virtualscroll CSS は埋め込まない
|
|
4
|
+
* (ホストが @aiquants/virtualscroll の Artifact A を別途 import するため。二重同梱を回避)。
|
|
5
|
+
* :root テーマ変数・ユーティリティ・preflight を含まない。
|
|
6
|
+
*/
|
|
7
|
+
@import "tailwindcss/theme.css" theme(reference);
|
|
8
|
+
@import "./directory-tree.css";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
@layer utilities {
|
|
2
|
+
/* ===== TreeGrid (columns) mode ===== */
|
|
3
|
+
/* Frozen name column on the left; numeric columns scroll horizontally via the shared
|
|
4
|
+
--dt-hscroll custom property. Header / body rows / footer all read the same variable so
|
|
5
|
+
they stay perfectly aligned without any per-row React re-render.
|
|
6
|
+
左に凍結した名前列、数値列は共有変数 --dt-hscroll で横スクロール。ヘッダ/本体/フッタが同一変数を
|
|
7
|
+
参照するため、行の再レンダー無しで整列を保つ。 */
|
|
8
|
+
|
|
9
|
+
.dt-grid {
|
|
10
|
+
position: relative;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* The virtualized body region. Its height (not the whole container's) is what the viewport
|
|
16
|
+
measurement must use, so header/footer are excluded. / 計測対象はこの本体領域の高さ。 */
|
|
17
|
+
.dt-grid-body {
|
|
18
|
+
position: relative;
|
|
19
|
+
flex: 1 1 0%;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dt-grid-header,
|
|
24
|
+
.dt-grid-footer,
|
|
25
|
+
.dt-grid-row,
|
|
26
|
+
.dt-grid-hscrollbar-row {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: stretch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Frozen name cell/header/footer: fixed width, above the numeric track (z-index), clips its
|
|
32
|
+
own overflow. / 凍結名前セル: 固定幅・数値トラックより前面・自身をクリップ。 */
|
|
33
|
+
.dt-grid-name-cell,
|
|
34
|
+
.dt-grid-name-header,
|
|
35
|
+
.dt-grid-name-footer {
|
|
36
|
+
position: relative;
|
|
37
|
+
z-index: 1;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Scrollable numeric region: a per-element viewport clips the translated track's overflow on
|
|
45
|
+
both sides (so it never slides under the frozen name cell). / 行ごとのビューポートで両端クリップ。 */
|
|
46
|
+
.dt-grid-numeric-viewport {
|
|
47
|
+
position: relative;
|
|
48
|
+
flex: 1 1 0%;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.dt-grid-numeric-track {
|
|
53
|
+
display: grid;
|
|
54
|
+
transform: translateX(calc(-1 * var(--dt-hscroll, 0px)));
|
|
55
|
+
will-change: transform;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dt-grid-cell {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
padding: 0 8px;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
text-overflow: ellipsis;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Right-aligned / centered numeric cells. / 右寄せ・中央寄せセル。 */
|
|
68
|
+
.dt-grid-cell--right {
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
text-align: right;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dt-grid-cell--center {
|
|
74
|
+
justify-content: center;
|
|
75
|
+
text-align: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.dt-grid-name-label {
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Fixed-width spacers used to keep the horizontal scrollbar aligned with the numeric viewport
|
|
85
|
+
(left = name width, right = vertical scrollbar width). / 横バー整合用スペーサ。 */
|
|
86
|
+
.dt-grid-scrollbar-spacer {
|
|
87
|
+
flex-shrink: 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*! @aiquants/directory-tree standalone CSS — 非 Tailwind ホスト専用。ホストの Tailwind ビルドと混在させないこと */
|
|
2
|
+
/*
|
|
3
|
+
* Artifact B: 自己完結スタンドアロン. 非 Tailwind ホストの利便のため peer の
|
|
4
|
+
* @aiquants/virtualscroll CSS も同梱する (Tailwind ホストは Artifact A を使うので二重にならない)。
|
|
5
|
+
*/
|
|
6
|
+
@layer theme, base, components, utilities;
|
|
7
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
8
|
+
@import "tailwindcss/utilities.css" layer(utilities) source(none);
|
|
9
|
+
@import "@aiquants/virtualscroll/styles/virtualscroll.css" layer(components);
|
|
10
|
+
@import "./directory-tree.css" layer(components);
|
|
11
|
+
@source "../**/*.{ts,tsx}";
|
|
12
|
+
@custom-variant dark (&:where(.dark, .dark *));
|