Skip to content

Commit 2c30e83

Browse files
Merge pull request #5 from phillip-kruger/main
Add default style and optional context menu
2 parents 714decd + e8b28ed commit 2c30e83

File tree

2 files changed

+110
-20
lines changed

2 files changed

+110
-20
lines changed

example/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ <h1>Directory Tree</h1>
7474
console.log('Is it a file?', event.detail.isFile);
7575
console.log('Node type:', event.detail.nodeType);
7676
});
77+
78+
treeComponent.contextMenuItems = [
79+
{
80+
title: 'Open',
81+
callback: (filePath, node) => console.log(`Opening ${filePath}`)
82+
},
83+
{
84+
title: 'Delete',
85+
callback: (filePath, node) => console.log(`Deleting ${filePath}`)
86+
}
87+
];
7788
</script>
7889
</body>
7990
</html>

qui-directory-tree.js

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,31 @@ import { LitElement, html, css } from 'lit';
22

33
class QuiDirectoryTree extends LitElement {
44
static properties = {
5-
directory: { type: Array }, // Directory data
6-
selectedPath: { type: String } // Currently selected path
5+
directory: { type: Array },
6+
selectedPath: { type: String },
7+
folderSelectable: { type: Boolean },
8+
contextMenuItems: { type: Array } // Context menu items for files
79
};
810

911
constructor() {
1012
super();
1113
this.directory = [];
1214
this.selectedPath = '';
13-
this._collapsedPaths = new Set(); // Track collapsed nodes
15+
this.folderSelectable = false;
16+
this.contextMenuItems = []; // Default: no context menu
17+
this._collapsedPaths = new Set();
1418
}
1519

1620
static styles = css`
1721
:host {
18-
--tree-node-font-family: 'Arial', sans-serif;
19-
--tree-node-font-size: 14px;
20-
--tree-icon-size: 16px;
21-
--tree-node-bg-hover: #f0f0f0;
22-
--tree-node-bg-selected: #d0e8ff;
23-
--tree-node-color: black;
24-
--tree-node-selected-color: black;
22+
23+
--tree-node-font-family: var(--lumo-font-family, 'Arial', sans-serif);
24+
--tree-node-font-size: var(--lumo-font-size-m, 14px);
25+
--tree-icon-size: var(--lumo-icon-size-s, 16px);
26+
--tree-node-bg-hover: var(--lumo-contrast-5pct, #f0f0f0);
27+
--tree-node-bg-selected: var(--lumo-primary-color-50pct, #d0e8ff);
28+
--tree-node-color: var(--lumo-body-text-color, black);
29+
--tree-node-selected-color: var(--lumo-body-text-color, black);
2530
--folder-icon-closed: 📁;
2631
--folder-icon-open: 📂;
2732
--file-icon: 📄;
@@ -34,7 +39,6 @@ class QuiDirectoryTree extends LitElement {
3439
font-size: var(--tree-node-font-size);
3540
}
3641
.node {
37-
cursor: pointer;
3842
padding: 5px;
3943
border-radius: 5px;
4044
display: flex;
@@ -52,31 +56,64 @@ class QuiDirectoryTree extends LitElement {
5256
.icon {
5357
font-size: var(--tree-icon-size);
5458
}
59+
60+
.label {
61+
cursor: pointer;
62+
}
63+
64+
.label.disabled {
65+
cursor: default;
66+
}
67+
68+
.context-menu {
69+
position: absolute;
70+
background: var(--lumo-base-color, white);
71+
border: 1px solid var(--lumo-base-color, white);
72+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
73+
z-index: 1000;
74+
}
75+
.context-menu-item {
76+
padding: 5px 10px;
77+
cursor: pointer;
78+
}
79+
.context-menu-item:hover {
80+
background-color: var(--tree-node-bg-hover);
81+
}
5582
`;
5683

5784
render() {
58-
return html`<ul class="tree">${this._renderTree(this.directory, '')}</ul>`;
85+
return html`
86+
<ul class="tree">${this._renderTree(this.directory, '')}</ul>
87+
${this._renderContextMenu()}
88+
`;
5989
}
6090

6191
_renderTree(nodes, currentPath) {
6292
return nodes.map((node) => {
6393
const path = currentPath ? `${currentPath}/${node.name}` : node.name;
6494
const isCollapsed = this._collapsedPaths.has(path);
95+
const isFolder = node.type === 'folder';
96+
const isSelectable = isFolder ? this.folderSelectable : true;
6597

6698
return html`
6799
<li>
68-
<div class="node ${this.selectedPath === path ? 'selected' : ''}">
69-
<span
70-
class="icon"
71-
@click="${(e) => this._toggleCollapse(e, path)}"
72-
>
73-
${node.type === 'folder'
100+
<div
101+
class="node ${this.selectedPath === path ? 'selected' : ''}"
102+
@contextmenu="${(e) => this._onContextMenu(e, path, node)}"
103+
>
104+
<span class="icon" @click="${(e) => this._toggleCollapse(e, path)}">
105+
${isFolder
74106
? isCollapsed
75107
? this._getIcon('folder-icon-closed')
76108
: this._getIcon('folder-icon-open')
77109
: this._getIcon('file-icon')}
78110
</span>
79-
<span @click="${(e) => this._onNodeClick(e, path, node)}">${node.name}</span>
111+
<span
112+
class="label ${!isSelectable ? 'disabled' : ''}"
113+
@click="${isSelectable ? (e) => this._onNodeClick(e, path, node) : null}"
114+
>
115+
${node.name}
116+
</span>
80117
</div>
81118
${node.children && !isCollapsed
82119
? html`<ul class="tree">${this._renderTree(node.children, path)}</ul>`
@@ -86,6 +123,26 @@ class QuiDirectoryTree extends LitElement {
86123
});
87124
}
88125

126+
_renderContextMenu() {
127+
if (!this._contextMenuData) return '';
128+
const { x, y, filePath, node } = this._contextMenuData;
129+
130+
return html`
131+
<div class="context-menu" style="top: ${y}px; left: ${x}px;">
132+
${this.contextMenuItems.map(
133+
(item) => html`
134+
<div
135+
class="context-menu-item"
136+
@click="${() => this._onContextMenuItemClick(item, filePath, node)}"
137+
>
138+
${item.title}
139+
</div>
140+
`
141+
)}
142+
</div>
143+
`;
144+
}
145+
89146
_getIcon(variableName) {
90147
return getComputedStyle(this).getPropertyValue(`--${variableName}`).trim() || '📄';
91148
}
@@ -102,7 +159,10 @@ class QuiDirectoryTree extends LitElement {
102159

103160
_onNodeClick(event, path, node) {
104161
event.stopPropagation();
162+
if (node.type === 'folder' && !this.folderSelectable) return;
163+
105164
this.selectedPath = path;
165+
this._contextMenuData = null; // Hide context menu on selection
106166
this.dispatchEvent(
107167
new CustomEvent('file-select', {
108168
detail: {
@@ -116,6 +176,25 @@ class QuiDirectoryTree extends LitElement {
116176
);
117177
}
118178

179+
_onContextMenu(event, filePath, node) {
180+
event.preventDefault();
181+
if (node.type !== 'file' || this.contextMenuItems.length === 0) return;
182+
183+
this._contextMenuData = {
184+
x: event.clientX,
185+
y: event.clientY,
186+
filePath,
187+
node
188+
};
189+
this.requestUpdate();
190+
}
191+
192+
_onContextMenuItemClick(item, filePath, node) {
193+
this._contextMenuData = null;
194+
this.requestUpdate();
195+
if (item.callback) item.callback(filePath, node);
196+
}
197+
119198
selectFile(filePath) {
120199
this._expandToPath(filePath);
121200
this.selectedPath = filePath;
@@ -132,7 +211,7 @@ class QuiDirectoryTree extends LitElement {
132211
}
133212

134213
expandAll() {
135-
this._collapsedPaths.clear(); // Remove all paths from the collapsed set
214+
this._collapsedPaths.clear();
136215
this.requestUpdate();
137216
}
138217

0 commit comments

Comments
 (0)