primevue-mirror/components/tree/TreeNode.vue

304 lines
11 KiB
Vue
Raw Normal View History

2022-09-06 12:03:37 +00:00
<template>
2022-09-14 11:26:01 +00:00
<li :class="containerClass" role="treeitem" :aria-label="label(node)" :aria-selected="selected" :aria-expanded="expanded" :aria-setsize="node.children ? node.children.length : 0" :aria-posinset="index + 1" :aria-level="level">
<div :class="contentClass" tabindex="0" role="treeitem" :aria-expanded="expanded" @click="onClick" @keydown="onKeyDown" @touchend="onTouchEnd" :style="node.style">
<button v-ripple type="button" class="p-tree-toggler p-link" @click="toggle" tabindex="-1">
2022-09-06 12:03:37 +00:00
<span :class="toggleIcon"></span>
</button>
2022-09-14 11:26:01 +00:00
<div v-if="checkboxMode" class="p-checkbox p-component">
2022-09-06 12:03:37 +00:00
<div :class="checkboxClass" role="checkbox" :aria-checked="checked">
<span :class="checkboxIcon"></span>
</div>
</div>
<span :class="icon"></span>
<span class="p-treenode-label">
2022-09-14 11:26:01 +00:00
<component v-if="templates[node.type] || templates['default']" :is="templates[node.type] || templates['default']" :node="node" />
<template v-else>{{ label(node) }}</template>
2022-09-06 12:03:37 +00:00
</span>
</div>
2022-09-14 11:26:01 +00:00
<ul v-if="hasChildren && expanded" class="p-treenode-children" role="group">
<TreeNode
v-for="childNode of node.children"
:key="childNode.key"
:node="childNode"
:templates="templates"
:level="level + 1"
:expandedKeys="expandedKeys"
@node-toggle="onChildNodeToggle"
@node-click="onChildNodeClick"
:selectionMode="selectionMode"
:selectionKeys="selectionKeys"
@checkbox-change="propagateUp"
/>
2022-09-06 12:03:37 +00:00
</ul>
</li>
</template>
<script>
2022-09-14 11:26:01 +00:00
import { DomHandler } from 'primevue/utils';
2022-09-06 12:03:37 +00:00
import Ripple from 'primevue/ripple';
export default {
name: 'TreeNode',
emits: ['node-toggle', 'node-click', 'checkbox-change'],
props: {
node: {
type: null,
default: null
},
expandedKeys: {
type: null,
default: null
},
selectionKeys: {
type: null,
default: null
},
selectionMode: {
type: String,
default: null
},
templates: {
type: null,
default: null
},
level: {
type: Number,
default: null
},
index: {
type: Number,
default: null
}
},
nodeTouched: false,
methods: {
toggle() {
this.$emit('node-toggle', this.node);
},
label(node) {
2022-09-14 11:26:01 +00:00
return typeof node.label === 'function' ? node.label() : node.label;
2022-09-06 12:03:37 +00:00
},
onChildNodeToggle(node) {
this.$emit('node-toggle', node);
},
onClick(event) {
if (DomHandler.hasClass(event.target, 'p-tree-toggler') || DomHandler.hasClass(event.target.parentElement, 'p-tree-toggler')) {
return;
}
if (this.isCheckboxSelectionMode()) {
this.toggleCheckbox();
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
this.$emit('node-click', {
originalEvent: event,
nodeTouched: this.nodeTouched,
node: this.node
});
}
this.nodeTouched = false;
},
onChildNodeClick(event) {
this.$emit('node-click', event);
},
onTouchEnd() {
this.nodeTouched = true;
},
onKeyDown(event) {
const nodeElement = event.target.parentElement;
switch (event.code) {
case 'ArrowDown':
var listElement = nodeElement.children[1];
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (listElement) {
this.focusNode(listElement.children[0]);
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
const nextNodeElement = nodeElement.nextElementSibling;
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (nextNodeElement) {
this.focusNode(nextNodeElement);
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
let nextSiblingAncestor = this.findNextSiblingOfAncestor(nodeElement);
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (nextSiblingAncestor) {
this.focusNode(nextSiblingAncestor);
}
}
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'ArrowUp':
if (nodeElement.previousElementSibling) {
this.focusNode(this.findLastVisibleDescendant(nodeElement.previousElementSibling));
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
let parentNodeElement = this.getParentNodeElement(nodeElement);
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (parentNodeElement) {
this.focusNode(parentNodeElement);
}
}
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'ArrowRight':
case 'ArrowLeft':
this.$emit('node-toggle', this.node);
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
case 'Enter':
case 'Space':
this.onClick(event);
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
default:
//no op
2022-09-14 11:26:01 +00:00
break;
2022-09-06 12:03:37 +00:00
}
event.preventDefault();
},
toggleCheckbox() {
2022-09-14 11:26:01 +00:00
let _selectionKeys = this.selectionKeys ? { ...this.selectionKeys } : {};
2022-09-06 12:03:37 +00:00
const _check = !this.checked;
this.propagateDown(this.node, _check, _selectionKeys);
this.$emit('checkbox-change', {
node: this.node,
check: _check,
selectionKeys: _selectionKeys
});
},
propagateDown(node, check, selectionKeys) {
2022-09-14 11:26:01 +00:00
if (check) selectionKeys[node.key] = { checked: true, partialChecked: false };
else delete selectionKeys[node.key];
2022-09-06 12:03:37 +00:00
if (node.children && node.children.length) {
for (let child of node.children) {
this.propagateDown(child, check, selectionKeys);
}
}
},
propagateUp(event) {
let check = event.check;
2022-09-14 11:26:01 +00:00
let _selectionKeys = { ...event.selectionKeys };
2022-09-06 12:03:37 +00:00
let checkedChildCount = 0;
let childPartialSelected = false;
for (let child of this.node.children) {
2022-09-14 11:26:01 +00:00
if (_selectionKeys[child.key] && _selectionKeys[child.key].checked) checkedChildCount++;
else if (_selectionKeys[child.key] && _selectionKeys[child.key].partialChecked) childPartialSelected = true;
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
if (check && checkedChildCount === this.node.children.length) {
_selectionKeys[this.node.key] = { checked: true, partialChecked: false };
} else {
2022-09-06 12:03:37 +00:00
if (!check) {
delete _selectionKeys[this.node.key];
}
2022-09-14 11:26:01 +00:00
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.node.key] = { checked: false, partialChecked: true };
else delete _selectionKeys[this.node.key];
2022-09-06 12:03:37 +00:00
}
this.$emit('checkbox-change', {
node: event.node,
check: event.check,
selectionKeys: _selectionKeys
});
},
onChildCheckboxChange(event) {
this.$emit('checkbox-change', event);
},
findNextSiblingOfAncestor(nodeElement) {
let parentNodeElement = this.getParentNodeElement(nodeElement);
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (parentNodeElement) {
2022-09-14 11:26:01 +00:00
if (parentNodeElement.nextElementSibling) return parentNodeElement.nextElementSibling;
else return this.findNextSiblingOfAncestor(parentNodeElement);
} else {
2022-09-06 12:03:37 +00:00
return null;
}
},
findLastVisibleDescendant(nodeElement) {
const childrenListElement = nodeElement.children[1];
2022-09-14 11:26:01 +00:00
2022-09-06 12:03:37 +00:00
if (childrenListElement) {
const lastChildElement = childrenListElement.children[childrenListElement.children.length - 1];
return this.findLastVisibleDescendant(lastChildElement);
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
return nodeElement;
}
},
getParentNodeElement(nodeElement) {
const parentNodeElement = nodeElement.parentElement.parentElement;
return DomHandler.hasClass(parentNodeElement, 'p-treenode') ? parentNodeElement : null;
},
focusNode(element) {
element.children[0].focus();
},
isCheckboxSelectionMode() {
return this.selectionMode === 'checkbox';
}
},
computed: {
hasChildren() {
return this.node.children && this.node.children.length > 0;
},
expanded() {
return this.expandedKeys && this.expandedKeys[this.node.key] === true;
},
leaf() {
return this.node.leaf === false ? false : !(this.node.children && this.node.children.length);
},
selectable() {
return this.node.selectable === false ? false : this.selectionMode != null;
},
selected() {
2022-09-14 11:26:01 +00:00
return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.node.key] === true : false;
2022-09-06 12:03:37 +00:00
},
containerClass() {
2022-09-14 11:26:01 +00:00
return ['p-treenode', { 'p-treenode-leaf': this.leaf }];
2022-09-06 12:03:37 +00:00
},
contentClass() {
2022-09-14 11:26:01 +00:00
return [
'p-treenode-content',
this.node.styleClass,
{
'p-treenode-selectable': this.selectable,
'p-highlight': this.checkboxMode ? this.checked : this.selected
}
];
2022-09-06 12:03:37 +00:00
},
icon() {
return ['p-treenode-icon', this.node.icon];
},
toggleIcon() {
return ['p-tree-toggler-icon pi pi-fw', this.expanded ? this.node.expandedIcon || 'pi-chevron-down' : this.node.collapsedIcon || 'pi-chevron-right'];
},
checkboxClass() {
2022-09-14 11:26:01 +00:00
return ['p-checkbox-box', { 'p-highlight': this.checked, 'p-indeterminate': this.partialChecked }];
2022-09-06 12:03:37 +00:00
},
checkboxIcon() {
2022-09-14 11:26:01 +00:00
return ['p-checkbox-icon', { 'pi pi-check': this.checked, 'pi pi-minus': this.partialChecked }];
2022-09-06 12:03:37 +00:00
},
checkboxMode() {
return this.selectionMode === 'checkbox' && this.node.selectable !== false;
},
checked() {
2022-09-14 11:26:01 +00:00
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].checked : false;
2022-09-06 12:03:37 +00:00
},
partialChecked() {
2022-09-14 11:26:01 +00:00
return this.selectionKeys ? this.selectionKeys[this.node.key] && this.selectionKeys[this.node.key].partialChecked : false;
2022-09-06 12:03:37 +00:00
}
},
directives: {
2022-09-14 11:26:01 +00:00
ripple: Ripple
2022-09-06 12:03:37 +00:00
}
2022-09-14 11:26:01 +00:00
};
2022-09-06 12:03:37 +00:00
</script>