Files
tp-tree-nav/mixins/tree-context-menu.js
2025-12-28 23:06:37 +01:00

117 lines
3.3 KiB
JavaScript

export const TreeContextMenuMixin = (superClass) => class TreeContextMenuMixin extends superClass {
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,
composed: true,
cancelable: true,
});
this.dispatchEvent(contextEvent);
if (contextEvent.defaultPrevented) return;
const baseActions = this._mergeActions(
this.defaultActions,
this._normalizeActions(item.node?.actions)
);
const maybeModified = this.beforeContextMenu
? this.beforeContextMenu(item, baseActions)
: baseActions;
const actions = await Promise.resolve(maybeModified);
if (actions === false) return;
const x = originalEvent.clientX;
const y = originalEvent.clientY;
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();
}
_onMenuAction(action, item, originalEvent) {
originalEvent.stopPropagation();
this._dispatchAction(action?.action, item, 'context-menu', originalEvent);
this._closeContextMenu();
}
_attachOutsideHandlers() {
if (this._outsideHandler) return;
this._outsideHandler = (e) => {
const menu = this.shadowRoot.querySelector('.context-menu');
if (menu && e.composedPath().includes(menu)) return;
this._closeContextMenu();
};
this._keyHandler = (e) => {
if (e.key === 'Escape') {
this._closeContextMenu();
}
};
window.addEventListener('pointerdown', this._outsideHandler, true);
window.addEventListener('keydown', this._keyHandler, true);
}
_removeOutsideHandlers() {
if (this._outsideHandler) {
window.removeEventListener('pointerdown', this._outsideHandler, true);
this._outsideHandler = null;
}
if (this._keyHandler) {
window.removeEventListener('keydown', this._keyHandler, true);
this._keyHandler = null;
}
}
_closeContextMenu() {
this._contextMenu = null;
this.requestUpdate();
this._removeOutsideHandlers();
}
};