@playcanvas/pcui
Version:
User interface component library for the web
1,015 lines (1,012 loc) • 37.8 kB
JavaScript
import { searchItems } from '../../helpers/search.mjs';
import { Container } from '../Container/index.mjs';
import { Element } from '../Element/index.mjs';
import { TreeViewItem } from '../TreeViewItem/index.mjs';
const CLASS_ROOT = 'pcui-treeview';
const CLASS_DRAGGED_ITEM = `${CLASS_ROOT}-item-dragged`;
const CLASS_DRAGGED_HANDLE = `${CLASS_ROOT}-drag-handle`;
const CLASS_FILTERING = `${CLASS_ROOT}-filtering`;
const CLASS_FILTER_RESULT = `${CLASS_FILTERING}-result`;
const DRAG_AREA_INSIDE = 'inside';
const DRAG_AREA_BEFORE = 'before';
const DRAG_AREA_AFTER = 'after';
/**
* A container that can show a TreeView like a hierarchy. The TreeView contains
* a hierarchy of {@link TreeViewItem}s.
*/
class TreeView extends Container {
/**
* Creates a new TreeView.
*
* @param args - The arguments.
*/
constructor(args = {}) {
var _a, _b, _c, _d;
super(args);
/**
* Determines whether reordering {@link TreeViewItem}s is allowed.
*/
this.allowReordering = true;
/**
* Determines whether renaming {@link TreeViewItem}s is allowed by double clicking on them.
*/
this.allowRenaming = false;
this._selectedItems = [];
this._dragItems = [];
this._dragging = false;
this._dragOverItem = null;
this._dragArea = DRAG_AREA_INSIDE;
this._dragScroll = 0;
this._dragScrollInterval = null;
this._pressedCtrl = false;
this._pressedShift = false;
this._filter = null;
this._filterResults = [];
this._updateModifierKeys = (evt) => {
this._pressedCtrl = evt.ctrlKey || evt.metaKey;
this._pressedShift = evt.shiftKey;
};
// Called when the mouse cursor leaves the tree view.
this._onMouseLeave = (evt) => {
if (!this._allowDrag || !this._dragging)
return;
this._dragOverItem = null;
this._updateDragHandle();
};
// Called when the mouse moves while dragging
this._onMouseMove = (evt) => {
if (!this._dragging)
return;
// Determine if we need to scroll the treeview if we are dragging towards the edges
const rect = this.dom.getBoundingClientRect();
this._dragScroll = 0;
let top = rect.top;
let bottom = rect.bottom;
if (this._dragScrollElement !== this) {
const dragScrollRect = this._dragScrollElement.dom.getBoundingClientRect();
top = Math.max(top + this._dragScrollElement.dom.scrollTop, dragScrollRect.top);
bottom = Math.min(bottom + this._dragScrollElement.dom.scrollTop, dragScrollRect.bottom);
}
top = Math.max(0, top);
bottom = Math.min(bottom, document.body.clientHeight);
if (evt.pageY < top + 32 && this._dragScrollElement.dom.scrollTop > 0) {
this._dragScroll = -1;
}
else if (evt.pageY > bottom - 32 && this._dragScrollElement.dom.scrollHeight > this._dragScrollElement.height + this._dragScrollElement.dom.scrollTop) {
this._dragScroll = 1;
}
};
// Called while we drag the drag handle
this._onDragMove = (evt) => {
evt.preventDefault();
evt.stopPropagation();
if (!this._allowDrag || !this._dragOverItem)
return;
const rect = this._dragHandle.dom.getBoundingClientRect();
const area = Math.floor((evt.clientY - rect.top) / rect.height * 5);
const oldArea = this._dragArea;
const oldDragOver = this._dragOverItem;
if (this._dragOverItem.parent === this) {
let parent = false;
for (let i = 0; i < this._dragItems.length; i++) {
if (this._dragItems[i].parent === this._dragOverItem) {
parent = true;
this._dragOverItem = null;
break;
}
}
if (!parent) {
this._dragArea = DRAG_AREA_INSIDE;
}
}
else {
// check if we are trying to drag item inside any of its children
let invalid = false;
for (let i = 0; i < this._dragItems.length; i++) {
if (this._dragItems[i].dom.contains(this._dragOverItem.dom)) {
invalid = true;
break;
}
}
if (invalid) {
this._dragOverItem = null;
}
else if (this.allowReordering && area <= 1 && this._dragItems.indexOf(this._dragOverItem.previousSibling) === -1) {
this._dragArea = DRAG_AREA_BEFORE;
}
else if (this.allowReordering && area >= 4 && this._dragItems.indexOf(this._dragOverItem.nextSibling) === -1 && (this._dragOverItem.numChildren === 0 || !this._dragOverItem.open)) {
this._dragArea = DRAG_AREA_AFTER;
}
else {
let parent = false;
if (this.allowReordering && this._dragOverItem.open) {
for (let i = 0; i < this._dragItems.length; i++) {
if (this._dragItems[i].parent === this._dragOverItem) {
parent = true;
this._dragArea = DRAG_AREA_BEFORE;
break;
}
}
}
if (!parent) {
this._dragArea = DRAG_AREA_INSIDE;
}
}
}
if (oldArea !== this._dragArea || oldDragOver !== this._dragOverItem) {
this._updateDragHandle();
}
};
this.class.add(CLASS_ROOT);
this._allowDrag = (_a = args.allowDrag) !== null && _a !== void 0 ? _a : true;
this.allowReordering = (_b = args.allowReordering) !== null && _b !== void 0 ? _b : true;
this.allowRenaming = (_c = args.allowRenaming) !== null && _c !== void 0 ? _c : false;
this._dragHandle = new Element({
class: CLASS_DRAGGED_HANDLE,
hidden: true
});
this._dragScrollElement = (_d = args.dragScrollElement) !== null && _d !== void 0 ? _d : this;
this.append(this._dragHandle);
this._onContextMenu = args.onContextMenu;
this._onReparentFn = args.onReparent;
this._wasDraggingAllowedBeforeFiltering = this._allowDrag;
window.addEventListener('keydown', this._updateModifierKeys);
window.addEventListener('keyup', this._updateModifierKeys);
window.addEventListener('mousedown', this._updateModifierKeys);
this.dom.addEventListener('mouseleave', this._onMouseLeave);
this._dragHandle.dom.addEventListener('mousemove', this._onDragMove);
this._dragHandle.on('destroy', (dom) => {
dom.removeEventListener('mousemove', this._onDragMove);
});
}
destroy() {
if (this._destroyed)
return;
window.removeEventListener('keydown', this._updateModifierKeys);
window.removeEventListener('keyup', this._updateModifierKeys);
window.removeEventListener('mousedown', this._updateModifierKeys);
window.removeEventListener('mousemove', this._onMouseMove);
this.dom.removeEventListener('mouseleave', this._onMouseLeave);
if (this._dragScrollInterval) {
window.clearInterval(this._dragScrollInterval);
this._dragScrollInterval = null;
}
super.destroy();
}
/**
* Finds the next tree item that is not currently hidden.
*
* @param currentItem - The current tree item.
* @returns The next visible tree item.
*/
_findNextVisibleTreeItem(currentItem) {
if (currentItem.numChildren > 0 && currentItem.open) {
return currentItem.firstChild;
}
const sibling = currentItem.nextSibling;
if (sibling)
return sibling;
let parent = currentItem.parent;
if (!(parent instanceof TreeViewItem))
return null;
let parentSibling = parent.nextSibling;
while (!parentSibling) {
parent = parent.parent;
if (!(parent instanceof TreeViewItem)) {
break;
}
parentSibling = parent.nextSibling;
}
return parentSibling;
}
/**
* Finds the last visible child tree item of the specified tree item.
*
* @param currentItem - The current item.
* @returns The last child item.
*/
_findLastVisibleChildTreeItem(currentItem) {
if (!currentItem.numChildren || !currentItem.open)
return null;
let lastChild = currentItem.lastChild;
while (lastChild && lastChild.numChildren && lastChild.open) {
lastChild = lastChild.lastChild;
}
return lastChild;
}
/**
* Finds the previous visible tree item of the specified tree item.
*
* @param currentItem - The current tree item.
* @returns The previous item.
*/
_findPreviousVisibleTreeItem(currentItem) {
const sibling = currentItem.previousSibling;
if (sibling) {
if (sibling.numChildren > 0 && sibling.open) {
return this._findLastVisibleChildTreeItem(sibling);
}
return sibling;
}
const parent = currentItem.parent;
if (!(parent instanceof TreeViewItem))
return null;
return parent;
}
/**
* Gets the visible tree items between the specified start and end tree items.
*
* @param startChild - The start tree item.
* @param endChild - The end tree item.
*/
_getChildrenRange(startChild, endChild) {
const result = [];
// select search results if we are currently filtering tree view items
if (this._filterResults.length) {
const filterResults = this.dom.querySelectorAll(`.${CLASS_ROOT}-item.${CLASS_FILTER_RESULT}`);
let startIndex = -1;
let endIndex = -1;
for (let i = 0; i < filterResults.length; i++) {
const item = filterResults[i].ui;
if (item === startChild) {
startIndex = i;
}
else if (item === endChild) {
endIndex = i;
}
if (startIndex !== -1 && endIndex !== -1) {
const start = (startIndex < endIndex ? startIndex : endIndex);
const end = (startIndex < endIndex ? endIndex : startIndex);
for (let j = start; j <= end; j++) {
result.push(filterResults[j].ui);
}
break;
}
}
}
else {
// if we are not filtering the tree view then find the next visible tree item
let current = startChild;
const rectStart = startChild.dom.getBoundingClientRect();
const rectEnd = endChild.dom.getBoundingClientRect();
if (rectStart.top < rectEnd.top) {
while (current && current !== endChild) {
current = this._findNextVisibleTreeItem(current);
if (current && current !== endChild) {
result.push(current);
}
}
}
else {
while (current && current !== endChild) {
current = this._findPreviousVisibleTreeItem(current);
if (current && current !== endChild) {
result.push(current);
}
}
}
result.push(endChild);
}
return result;
}
_onAppendChild(element) {
super._onAppendChild(element);
if (element instanceof TreeViewItem) {
this._onAppendTreeViewItem(element);
}
}
_onRemoveChild(element) {
if (element instanceof TreeViewItem) {
this._onRemoveTreeViewItem(element);
}
super._onRemoveChild(element);
}
_onAppendTreeViewItem(item) {
item.treeView = this;
if (this._filter) {
// add new item to filtered results if it
// satisfies the current filter
this._searchItems([item], this._filter);
}
// do the same for all children of the element
item.forEachChild((child) => {
if (child instanceof TreeViewItem) {
this._onAppendTreeViewItem(child);
}
});
}
_onRemoveTreeViewItem(item) {
item.selected = false;
// do the same for all children of the element
item.forEachChild((child) => {
if (child instanceof TreeViewItem) {
this._onRemoveTreeViewItem(child);
}
});
}
// Called when a key is down on a child TreeViewItem.
_onChildKeyDown(evt, item) {
if (['Tab', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].indexOf(evt.key) === -1)
return;
evt.preventDefault();
evt.stopPropagation();
switch (evt.key) {
case 'ArrowLeft': {
if (item.numChildren > 0 && item.open) {
// If item has children and is expanded, fold it
item.open = false;
}
else {
// If item is a leaf or already folded, select parent
const parent = item.parent;
if (parent instanceof TreeViewItem) {
this._selectSingleItem(parent);
}
}
break;
}
case 'ArrowRight': {
if (item.numChildren > 0) {
if (!item.open) {
// If item is folded, unfold it
item.open = true;
}
else {
// If item is already unfolded, select first child
const firstChild = item.firstChild;
if (firstChild instanceof TreeViewItem) {
this._selectSingleItem(firstChild);
}
}
}
break;
}
case 'ArrowDown': {
if (this._selectedItems.length) {
const next = this._findNextVisibleTreeItem(item);
if (next) {
if (this._pressedShift || this._pressedCtrl) {
next.selected = true;
}
else {
this._selectSingleItem(next);
}
}
}
break;
}
case 'ArrowUp': {
if (this._selectedItems.length) {
const prev = this._findPreviousVisibleTreeItem(item);
if (prev) {
if (this._pressedShift || this._pressedCtrl) {
prev.selected = true;
}
else {
this._selectSingleItem(prev);
}
}
}
break;
}
}
}
// Called when we click on a child TreeViewItem
_onChildClick(evt, item) {
if (evt.button !== 0)
return;
if (!item.allowSelect)
return;
if (this._pressedCtrl) {
// toggle selection when Ctrl is pressed
item.selected = !item.selected;
}
else if (this._pressedShift) {
// on shift add to selection
if (!this._selectedItems.length || this._selectedItems.length === 1 && this._selectedItems[0] === item) {
item.selected = true;
return;
}
const selected = this._selectedItems[this._selectedItems.length - 1];
this._openHierarchy(selected);
const children = this._getChildrenRange(selected, item);
children.forEach((child) => {
if (child.allowSelect) {
child.selected = true;
}
});
}
else {
// deselect other items
this._selectSingleItem(item);
}
}
/**
* Call specified function on every child TreeViewItem by traversing the hierarchy depth first.
*
* @param fn - The function to call. The function takes the TreeViewItem as an argument.
*/
_traverseDepthFirst(fn) {
function traverse(item) {
if (!item || !(item instanceof TreeViewItem))
return;
fn(item);
if (item.numChildren) {
for (const child of item.dom.childNodes) {
traverse(child.ui);
}
}
}
for (const child of this.dom.childNodes) {
traverse(child.ui);
}
}
/**
* Do a depth first traversal of all tree items
* and assign an order to them so that we know which one
* is above the other. Performance wise this means it traverses
* all tree items every time however seems to be pretty fast even with 15 - 20 K entities.
*/
_getTreeOrder() {
const treeOrder = new Map();
let order = 0;
this._traverseDepthFirst((item) => {
treeOrder.set(item, order++);
});
return treeOrder;
}
_getChildIndex(item, parent) {
return Array.prototype.indexOf.call(parent.dom.childNodes, item.dom) - 1;
}
// Called when we start dragging a TreeViewItem.
_onChildDragStart(evt, item) {
if (!this.allowDrag || this._dragging)
return;
this._dragItems = [];
if (this._selectedItems.indexOf(item) !== -1) {
const dragged = [];
// check that all selected items to be dragged are
// at the same depth from the root
let desiredDepth = -1;
for (let i = 0; i < this._selectedItems.length; i++) {
let parent = this._selectedItems[i].parent;
let depth = 0;
let isChild = false;
while (parent && parent instanceof TreeViewItem) {
// if parent is already in dragged items then skip
// depth calculation for this item
if (this._selectedItems.indexOf(parent) !== -1) {
isChild = true;
break;
}
depth++;
parent = parent.parent;
}
if (!isChild) {
if (desiredDepth === -1) {
desiredDepth = depth;
}
else if (desiredDepth !== depth) {
return;
}
dragged.push(this._selectedItems[i]);
}
}
// add dragged class to each item
this._dragItems = dragged;
}
else {
item.class.add(CLASS_DRAGGED_ITEM);
this._dragItems.push(item);
}
if (this._dragItems.length) {
this._dragItems.forEach((item) => {
item.class.add(CLASS_DRAGGED_ITEM);
});
this.isDragging = true;
this.emit('dragstart', this._dragItems.slice());
}
}
// Called when we stop dragging a TreeViewItem.
_onChildDragEnd(evt, item) {
if (!this.allowDrag || !this._dragging)
return;
this._dragItems.forEach(item => item.class.remove(CLASS_DRAGGED_ITEM));
// if the root is being dragged then
// do not allow reparenting because we do not
// want to reparent the root
let isRootDragged = false;
for (let i = 0; i < this._dragItems.length; i++) {
if (this._dragItems[i].parent === this) {
isRootDragged = true;
break;
}
}
if (!isRootDragged && this._dragOverItem) {
if (this._dragItems.length > 1) {
// sort items based on order in the hierarchy
const treeOrder = this._getTreeOrder();
this._dragItems.sort((a, b) => {
return treeOrder.get(a) - treeOrder.get(b);
});
}
if (this._dragItems.length) {
// reparent items
const reparented = [];
// if we do not have _onReparentFn then reparent all the dragged items
// in the DOM
if (!this._onReparentFn) {
// first remove all items from their parent
this._dragItems.forEach((item) => {
if (item.parent === this._dragOverItem && this._dragArea === DRAG_AREA_INSIDE)
return;
reparented.push({
item: item,
oldParent: item.parent
});
item.parent.remove(item);
});
// now reparent items
reparented.forEach((r, i) => {
if (this._dragArea === DRAG_AREA_BEFORE) {
// If dragged before a TreeViewItem...
r.newParent = this._dragOverItem.parent;
r.newParent.appendBefore(r.item, this._dragOverItem);
r.newChildIndex = this._getChildIndex(r.item, r.newParent);
}
else if (this._dragArea === DRAG_AREA_INSIDE) {
// If dragged inside a TreeViewItem...
r.newParent = this._dragOverItem;
r.newParent.append(r.item);
r.newParent.open = true;
r.newChildIndex = this._getChildIndex(r.item, r.newParent);
}
else if (this._dragArea === DRAG_AREA_AFTER) {
// If dragged after a TreeViewItem...
r.newParent = this._dragOverItem.parent;
r.newParent.appendAfter(r.item, i > 0 ? reparented[i - 1].item : this._dragOverItem);
r.newChildIndex = this._getChildIndex(r.item, r.newParent);
}
});
}
else {
// if we have an _onReparentFn then we will not perform the reparenting here
// but will instead calculate the new indexes and pass that data to the reparent function
// to perform the reparenting
const fakeDom = [];
const getChildren = (treeviewItem) => {
let idx = fakeDom.findIndex(entry => entry.parent === treeviewItem);
if (idx === -1) {
fakeDom.push({ parent: treeviewItem, children: [...treeviewItem.dom.childNodes] });
idx = fakeDom.length - 1;
}
return fakeDom[idx].children;
};
this._dragItems.forEach((item) => {
if (item.parent === this._dragOverItem && this._dragArea === DRAG_AREA_INSIDE)
return;
reparented.push({
item: item,
oldParent: item.parent
});
// add array of parent's child nodes to fakeDom array
const parentChildren = getChildren(item.parent);
// remove this item from the children array in fakeDom
const childIdx = parentChildren.indexOf(item.dom);
parentChildren.splice(childIdx, 1);
});
// now reparent items
reparented.forEach((r, i) => {
if (this._dragArea === DRAG_AREA_BEFORE) {
// If dragged before a TreeViewItem...
r.newParent = this._dragOverItem.parent;
const parentChildren = getChildren(this._dragOverItem.parent);
const index = parentChildren.indexOf(this._dragOverItem.dom);
parentChildren.splice(index, 0, r.item.dom);
r.newChildIndex = index;
}
else if (this._dragArea === DRAG_AREA_INSIDE) {
// If dragged inside a TreeViewItem...
r.newParent = this._dragOverItem;
const parentChildren = getChildren(this._dragOverItem);
parentChildren.push(r.item.dom);
r.newChildIndex = parentChildren.length - 1;
}
else if (this._dragArea === DRAG_AREA_AFTER) {
// If dragged after a TreeViewItem...
r.newParent = this._dragOverItem.parent;
const parentChildren = getChildren(this._dragOverItem.parent);
const after = i > 0 ? reparented[i - 1].item : this._dragOverItem;
const index = parentChildren.indexOf(after.dom);
parentChildren.splice(index + 1, 0, r.item.dom);
r.newChildIndex = index + 1;
}
// subtract 1 from new child index to account for the extra node that
// each tree view item has inside
r.newChildIndex--;
});
}
if (reparented.length) {
if (this._onReparentFn) {
this._onReparentFn(reparented);
}
this.emit('reparent', reparented);
}
}
}
this._dragItems = [];
this.isDragging = false;
this.emit('dragend');
}
// Called when we drag over a TreeViewItem.
_onChildDragOver(evt, item) {
if (!this._allowDrag || !this._dragging)
return;
if (item.allowDrop && this._dragItems.indexOf(item) === -1) {
this._dragOverItem = item;
}
else {
this._dragOverItem = null;
}
this._updateDragHandle();
this._onDragMove(evt);
}
// Scroll treeview if we are dragging towards the edges
_scrollWhileDragging() {
if (!this._dragging)
return;
if (this._dragScroll === 0)
return;
this._dragScrollElement.dom.scrollTop += this._dragScroll * 8;
this._dragOverItem = null;
this._updateDragHandle();
}
// Updates the drag handle position and size
_updateDragHandle(dragOverItem, force) {
if (!force && (!this._allowDrag || !this._dragging))
return;
if (!dragOverItem) {
dragOverItem = this._dragOverItem;
}
if (!dragOverItem || dragOverItem.hidden || !dragOverItem.parentsOpen) {
this._dragHandle.hidden = true;
}
else {
// @ts-ignore
const rect = dragOverItem._containerContents.dom.getBoundingClientRect();
this._dragHandle.hidden = false;
this._dragHandle.class.remove(DRAG_AREA_AFTER, DRAG_AREA_BEFORE, DRAG_AREA_INSIDE);
this._dragHandle.class.add(this._dragArea);
const top = rect.top;
let left = rect.left;
let width = rect.width;
if (this.dom.parentElement) {
const parentRect = this.dom.parentElement.getBoundingClientRect();
left = Math.max(left, parentRect.left);
width = Math.min(width, this.dom.parentElement.clientWidth - left + parentRect.left);
}
this._dragHandle.style.top = `${top}px`;
this._dragHandle.style.left = `${left}px`;
this._dragHandle.style.width = `${width - 7}px`;
}
}
/**
* Opens all the parents of the specified item.
*
* @param endItem - The end tree view item.
*/
_openHierarchy(endItem) {
endItem.parentsOpen = true;
}
/**
* Selects a tree view item.
*
* @param item - The tree view item.
*/
_selectSingleItem(item) {
let i = this._selectedItems.length;
let othersSelected = false;
while (i--) {
if (this._selectedItems[i] && this._selectedItems[i] !== item) {
this._selectedItems[i].selected = false;
othersSelected = true;
}
}
if (othersSelected) {
item.selected = true;
}
else {
item.selected = !item.selected;
}
}
/**
* Called when a child tree view item is selected.
*
* @param item - The tree view item.
*/
_onChildSelected(item) {
this._selectedItems.push(item);
this._openHierarchy(item);
this.emit('select', item);
}
/**
* Called when a child tree view item is deselected.
*
* @param item - The tree view item.
*/
_onChildDeselected(item) {
const index = this._selectedItems.indexOf(item);
if (index !== -1) {
this._selectedItems.splice(index, 1);
this.emit('deselect', item);
}
}
/**
* Called when a child tree view item is renamed.
*
* @param item - The tree view item.
* @param newName - The new name.
*/
_onChildRename(item, newName) {
if (this._filter) {
// unfilter this item
item.class.remove(CLASS_FILTER_RESULT);
const index = this._filterResults.indexOf(item);
if (index !== -1) {
this._filterResults.splice(index, 1);
}
// see if we can include it in the current filter
this._searchItems([item], this._filter);
}
this.emit('rename', item, newName);
}
_searchItems(items, filter) {
const results = searchItems(items, 'text', filter);
if (!results.length)
return;
results.forEach((item) => {
this._filterResults.push(item);
item.class.add(CLASS_FILTER_RESULT);
});
}
/**
* Searches the treeview.
*
* @param filter - The search filter.
*/
_applyFilter(filter) {
this._clearFilter();
this._wasDraggingAllowedBeforeFiltering = this._allowDrag;
this._allowDrag = false;
this.class.add(CLASS_FILTERING);
const items = [];
this._traverseDepthFirst((item) => {
items.push(item);
});
this._searchItems(items, filter);
}
/**
* Clears search filter.
*/
_clearFilter() {
this._filterResults.forEach((item) => {
if (item.destroyed)
return;
item.class.remove(CLASS_FILTER_RESULT);
});
this._filterResults.length = 0;
this.class.remove(CLASS_FILTERING);
this._allowDrag = this._wasDraggingAllowedBeforeFiltering;
}
/**
* Show the drag handle on the given tree item.
*
* @param treeItem - The tree item.
*/
showDragHandle(treeItem) {
this._updateDragHandle(treeItem, true);
}
/**
* Deselects all selected tree view items.
*/
deselect() {
let i = this._selectedItems.length;
while (i--) {
if (this._selectedItems[i]) {
this._selectedItems[i].selected = false;
}
}
}
/**
* Removes all child tree view items.
*/
clearTreeItems() {
let i = this.dom.childNodes.length;
while (i--) {
const dom = this.dom.childNodes[i];
if (!dom)
continue;
const ui = dom.ui;
if (ui instanceof TreeViewItem) {
ui.destroy();
}
}
this._selectedItems = [];
this._dragItems = [];
this._allowDrag = this._wasDraggingAllowedBeforeFiltering;
}
/**
* Sets whether dragging a TreeViewItem is allowed.
*/
set allowDrag(value) {
this._allowDrag = value;
if (this._filter) {
this._wasDraggingAllowedBeforeFiltering = value;
}
}
/**
* Gets whether dragging a TreeViewItem is allowed.
*/
get allowDrag() {
return this._allowDrag;
}
/**
* Sets whether a TreeViewItem is currently being dragged.
*/
set isDragging(value) {
if (this._dragging === value)
return;
if (value) {
this._dragging = true;
this._updateDragHandle();
// handle mouse move to scroll when dragging if necessary
if (this.scrollable || this._dragScrollElement !== this) {
window.removeEventListener('mousemove', this._onMouseMove);
window.addEventListener('mousemove', this._onMouseMove);
if (!this._dragScrollInterval) {
this._dragScrollInterval = window.setInterval(() => {
this._scrollWhileDragging();
}, 1000 / 60);
}
}
}
else {
this._dragOverItem = null;
this._updateDragHandle();
this._dragging = false;
window.removeEventListener('mousemove', this._onMouseMove);
if (this._dragScrollInterval) {
window.clearInterval(this._dragScrollInterval);
this._dragScrollInterval = null;
}
}
}
/**
* Gets whether a TreeViewItem is currently being dragged.
*/
get isDragging() {
return this._dragging;
}
/**
* Gets all of the currently selected TreeViewItems.
*/
get selected() {
return this._selectedItems.slice();
}
/**
* Sets the filter that searches TreeViewItems and only shows the ones that are relevant to the filter.
*/
set filter(value) {
if (this._filter === value)
return;
this._filter = value;
if (value) {
this._applyFilter(value);
}
else {
this._clearFilter();
}
}
/**
* Gets the filter that searches TreeViewItems and only shows the ones that are relevant to the filter.
*/
get filter() {
return this._filter;
}
/**
* Gets whether Ctrl is currently pressed.
*/
get pressedCtrl() {
return this._pressedCtrl;
}
/**
* Gets whether Shift is currently pressed.
*/
get pressedShift() {
return this._pressedShift;
}
}
/**
* Fired when user starts dragging selected TreeViewItems.
*
* @event
* @example
* ```ts
* const treeView = new TreeView({
* allowDrag: true // this is the default but we're showing it here for clarity
* });
* treeView.on('dragstart', (items) => {
* console.log(`Drag started of ${items.length} items');
* });
* ```
*/
TreeView.EVENT_DRAGSTART = 'dragstart';
/**
* Fired when user stops dragging selected TreeViewItems.
*
* @event
* @example
* ```ts
* const treeView = new TreeView({
* allowDrag: true // this is the default but we're showing it here for clarity
* });
* treeView.on('dragend', () => {
* console.log('Drag ended');
* });
* ```
*/
TreeView.EVENT_DRAGEND = 'dragend';
/**
* Fired when user reparents TreeViewItems.
*
* @event
* @example
* ```ts
* const treeView = new TreeView();
* treeView.on('reparent', (reparented: { item: TreeViewItem; oldParent: Element; }[]) => {
* console.log(`Reparented ${reparented.length} items`);
* });
* ```
*/
TreeView.EVENT_REPARENT = 'reparent';
/**
* Fired when user selects a TreeViewItem.
*
* @event
* @example
* ```ts
* const treeView = new TreeView();
* treeView.on('select', (item: TreeViewItem) => {
* console.log(`Selected item ${item.text}`);
* });
* ```
*/
TreeView.EVENT_SELECT = 'select';
/**
* Fired when user deselects a TreeViewItem.
*
* @event
* @example
* ```ts
* const treeView = new TreeView();
* treeView.on('deselect', (item: TreeViewItem) => {
* console.log(`Deselected item ${item.text}`);
* });
* ```
*/
TreeView.EVENT_DESELECT = 'deselect';
/**
* Fired when user renames a TreeViewItem.
*
* @event
* @example
* ```ts
* const treeView = new TreeView();
* treeView.on('rename', (item: TreeViewItem, name: string) => {
* console.log(`Renamed item to ${name}`);
* });
* ```
*/
TreeView.EVENT_RENAME = 'rename';
export { TreeView };
//# sourceMappingURL=index.mjs.map