wip
This commit is contained in:
136
tp-tree-nav.js
136
tp-tree-nav.js
@@ -7,7 +7,7 @@ This program is available under Apache License Version 2.0
|
||||
import { LitElement, html, css, svg } from 'lit';
|
||||
import { virtualize } from '@lit-labs/virtualizer/virtualize.js';
|
||||
|
||||
class TpTreeNav extends LitElement {
|
||||
export class TpTreeNav extends LitElement {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
@@ -31,6 +31,8 @@ class TpTreeNav extends LitElement {
|
||||
padding: 0 8px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
@@ -66,7 +68,6 @@ class TpTreeNav extends LitElement {
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 6px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 auto;
|
||||
@@ -140,6 +141,66 @@ class TpTreeNav extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
_renderItem(item) {
|
||||
const { node, depth, hasChildren, expanded } = item;
|
||||
const icon = this._resolveIcon(node?.icon);
|
||||
|
||||
const custom = this.renderNode?.(item, {
|
||||
depth,
|
||||
states: Array.isArray(node?.states) ? node.states : [],
|
||||
path: item.path,
|
||||
hasChildren,
|
||||
});
|
||||
|
||||
if (custom) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return html`
|
||||
<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)}
|
||||
>
|
||||
<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>`}
|
||||
<div class="label" part="label">${node?.label ?? ''}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderContextMenu() {
|
||||
if (!this._contextMenu) return null;
|
||||
const { x, y, 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;">
|
||||
${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>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
roots: { type: Array },
|
||||
@@ -217,44 +278,6 @@ class TpTreeNav extends LitElement {
|
||||
return results;
|
||||
}
|
||||
|
||||
_renderItem(item) {
|
||||
const { node, depth, hasChildren, expanded } = item;
|
||||
const icon = this._resolveIcon(node?.icon);
|
||||
|
||||
const custom = this.renderNode?.(node, {
|
||||
depth,
|
||||
states: Array.isArray(node?.states) ? node.states : [],
|
||||
path: item.path,
|
||||
hasChildren,
|
||||
});
|
||||
|
||||
if (custom) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return html`
|
||||
<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)}
|
||||
>
|
||||
<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>`}
|
||||
<div class="label" part="label">${node?.label ?? ''}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_onRowClick(item, originalEvent) {
|
||||
const ev = new CustomEvent('node-click', {
|
||||
detail: { node: item.node, path: item.path, originalEvent },
|
||||
@@ -272,8 +295,9 @@ class TpTreeNav extends LitElement {
|
||||
|
||||
async _onContextMenu(item, originalEvent) {
|
||||
originalEvent.preventDefault();
|
||||
|
||||
const contextEvent = new CustomEvent('node-context', {
|
||||
detail: { node: item.node, path: item.path, originalEvent },
|
||||
detail: { item, originalEvent },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
@@ -288,7 +312,7 @@ class TpTreeNav extends LitElement {
|
||||
);
|
||||
|
||||
const maybeModified = this.beforeContextMenu
|
||||
? this.beforeContextMenu(item.node, baseActions)
|
||||
? this.beforeContextMenu(item, baseActions)
|
||||
: baseActions;
|
||||
|
||||
const actions = await Promise.resolve(maybeModified);
|
||||
@@ -305,7 +329,7 @@ class TpTreeNav extends LitElement {
|
||||
|
||||
_dispatchAction(action, item, source, originalEvent) {
|
||||
const ev = new CustomEvent('node-action', {
|
||||
detail: { action, node: item.node, path: item.path, source, originalEvent },
|
||||
detail: { action, item, source, originalEvent },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
@@ -313,28 +337,6 @@ class TpTreeNav extends LitElement {
|
||||
this.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
_renderContextMenu() {
|
||||
if (!this._contextMenu) return null;
|
||||
const { x, y, 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;">
|
||||
${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>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_onMenuAction(action, item, originalEvent) {
|
||||
originalEvent.stopPropagation();
|
||||
this._dispatchAction(action?.action, item, 'context-menu', originalEvent);
|
||||
@@ -456,17 +458,21 @@ class TpTreeNav extends LitElement {
|
||||
const slug = node?.slug ?? `${index}`;
|
||||
const nextPath = [...path, slug];
|
||||
const mappedNode = mapper(node, nextPath) ?? node;
|
||||
|
||||
const { nodes: childNodes, changed: childChanged } = this._mapTree(
|
||||
node?.children,
|
||||
mapper,
|
||||
nextPath
|
||||
);
|
||||
|
||||
const children = childChanged ? childNodes : node?.children;
|
||||
const nodeChanged = mappedNode !== node || childChanged;
|
||||
|
||||
if (nodeChanged) {
|
||||
changed = true;
|
||||
return { ...mappedNode, children };
|
||||
}
|
||||
|
||||
return mappedNode;
|
||||
});
|
||||
return { nodes: mapped, changed };
|
||||
|
||||
Reference in New Issue
Block a user