wip
This commit is contained in:
123
tp-tree-nav.js
123
tp-tree-nav.js
@@ -4,10 +4,13 @@ Copyright (c) 2025 trading_peter
|
||||
This program is available under Apache License Version 2.0
|
||||
*/
|
||||
|
||||
import '@tp/tp-icon/tp-icon.js';
|
||||
import '@tp/tp-popup/tp-popup-menu.js';
|
||||
import { LitElement, html, css, svg } from 'lit';
|
||||
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
|
||||
import { Position } from '../helpers/position.js';
|
||||
|
||||
export class TpTreeNav extends LitElement {
|
||||
export class TpTreeNav extends Position(LitElement) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
@@ -98,40 +101,10 @@ export class TpTreeNav extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
tp-popup-menu.context-menu {
|
||||
min-width: 180px;
|
||||
background: var(--tp-tree-menu-bg, #fff);
|
||||
border: 1px solid var(--tp-tree-menu-border, rgba(0,0,0,0.1));
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.18);
|
||||
padding: 4px 0;
|
||||
pointer-events: auto;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: var(--tp-tree-menu-hover-bg, rgba(0,0,0,0.06));
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
`
|
||||
];
|
||||
@@ -173,20 +146,9 @@ export class TpTreeNav extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="row"
|
||||
part="row"
|
||||
style="--depth: ${depth}"
|
||||
@click=${(e) => this._onRowClick(item, e)}
|
||||
@contextmenu=${(e) => this._onContextMenu(item, e)}
|
||||
>
|
||||
<div class="row" part="row" style="--depth: ${depth}" @click=${(e) => this._onRowClick(item, e)} @contextmenu=${(e) => this._onContextMenu(item, e)} >
|
||||
<div class="indent"></div>
|
||||
<div
|
||||
class="chevron-btn ${expanded ? 'expanded' : ''}"
|
||||
part="chevron"
|
||||
?hidden=${!hasChildren}
|
||||
@click=${(e) => this._onChevronClick(item, e)}
|
||||
>
|
||||
<div class="chevron-btn ${expanded ? 'expanded' : ''}" part="chevron" ?hidden=${!hasChildren} @click=${(e) => this._onChevronClick(item, e)} >
|
||||
<tp-icon class="icon" part="chevron-icon" .icon=${TpTreeNav.chevron}></tp-icon>
|
||||
</div>
|
||||
${icon ? html`<tp-icon class="icon" part="icon" .icon=${icon}></tp-icon>` : html`<span class="icon" part="icon" aria-hidden="true"></span>`}
|
||||
@@ -212,22 +174,17 @@ export class TpTreeNav extends LitElement {
|
||||
|
||||
_renderContextMenu() {
|
||||
if (!this._contextMenu) return null;
|
||||
const { x, y, item, actions } = this._contextMenu;
|
||||
const { item, actions } = this._contextMenu;
|
||||
|
||||
return html`
|
||||
<div class="context-menu-overlay">
|
||||
<div class="context-menu" part="context-menu" style="left:${x}px; top:${y}px;">
|
||||
<tp-popup-menu class="context-menu" part="context-menu">
|
||||
${actions.map((action) => html`
|
||||
<button
|
||||
class="menu-item"
|
||||
part="context-menu-item"
|
||||
@click=${(e) => this._onMenuAction(action, item, e)}
|
||||
>
|
||||
${action?.icon ? html`<tp-icon class="menu-icon" part="context-menu-icon" .icon=${this._resolveIcon(action.icon)}></tp-icon>` : html`<span class="menu-icon" part="context-menu-icon" aria-hidden="true"></span>`}
|
||||
<span>${action?.label ?? ''}</span>
|
||||
</button>
|
||||
<tp-popup-menu-item .icon=${action.icon} part="context-menu-item" @click=${(e) => this._onMenuAction(action, item, e)}>
|
||||
${action?.label ?? null}
|
||||
</tp-popup-menu-item>
|
||||
`)}
|
||||
</div>
|
||||
</tp-popup-menu>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -257,6 +214,7 @@ export class TpTreeNav extends LitElement {
|
||||
emptyMessage: { type: String },
|
||||
showActions: { type: Boolean },
|
||||
expandOnDoubleClick: { type: Boolean },
|
||||
selectOnRightClick: { type: Boolean },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,6 +256,7 @@ export class TpTreeNav extends LitElement {
|
||||
this.emptyMessage = 'No items';
|
||||
this.showActions = false;
|
||||
this.expandOnDoubleClick = false;
|
||||
this.selectOnRightClick = false;
|
||||
this._contextMenu = null;
|
||||
this._outsideHandler = null;
|
||||
this._keyHandler = null;
|
||||
@@ -420,6 +379,37 @@ export class TpTreeNav extends LitElement {
|
||||
async _onContextMenu(item, originalEvent) {
|
||||
originalEvent.preventDefault();
|
||||
|
||||
if (this.selectOnRightClick && this.manageState) {
|
||||
const pathStr = item.path.join('/');
|
||||
const set = new Set(this._selectedPathSet);
|
||||
let changed = false;
|
||||
|
||||
if (this.multiSelect) {
|
||||
if (!set.has(pathStr)) {
|
||||
set.add(pathStr);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (!set.has(pathStr) || set.size !== 1) {
|
||||
set.clear();
|
||||
set.add(pathStr);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this._selectedPathSet = set;
|
||||
this.selectedPaths = Array.from(set);
|
||||
this._rebuildManagedTree();
|
||||
|
||||
this.dispatchEvent(new CustomEvent('node-selected', {
|
||||
detail: { node: item.node, path: item.path, originalEvent },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const contextEvent = new CustomEvent('node-context', {
|
||||
detail: { item, originalEvent },
|
||||
bubbles: true,
|
||||
@@ -442,12 +432,23 @@ export class TpTreeNav extends LitElement {
|
||||
const actions = await Promise.resolve(maybeModified);
|
||||
if (actions === false) return;
|
||||
|
||||
const rect = this.getBoundingClientRect();
|
||||
const x = originalEvent.clientX - rect.left;
|
||||
const y = originalEvent.clientY - rect.top;
|
||||
const x = originalEvent.clientX;
|
||||
const y = originalEvent.clientY;
|
||||
|
||||
this._contextMenu = { x, y, item, actions: Array.isArray(actions) ? actions : baseActions };
|
||||
this._contextMenu = { item, actions: Array.isArray(actions) ? actions : baseActions };
|
||||
this.requestUpdate();
|
||||
await this.updateComplete;
|
||||
|
||||
const menu = this.shadowRoot.querySelector('.context-menu');
|
||||
if (menu) {
|
||||
const anchor = {
|
||||
getBoundingClientRect: () => ({
|
||||
top: y, bottom: y, left: x, right: x, width: 0, height: 0
|
||||
})
|
||||
};
|
||||
this._posFixed(anchor, menu, { valign: 'bottom', halign: 'left' });
|
||||
}
|
||||
|
||||
this._attachOutsideHandlers();
|
||||
}
|
||||
|
||||
@@ -475,7 +476,7 @@ export class TpTreeNav extends LitElement {
|
||||
_attachOutsideHandlers() {
|
||||
if (this._outsideHandler) return;
|
||||
this._outsideHandler = (e) => {
|
||||
const menu = this.shadowRoot?.querySelector('.context-menu');
|
||||
const menu = this.shadowRoot.querySelector('.context-menu');
|
||||
if (menu && e.composedPath().includes(menu)) return;
|
||||
this._closeContextMenu();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user