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