primevue-mirror/components/lib/treetable/TreeTableRow.vue

456 lines
15 KiB
Vue
Raw Normal View History

2022-09-06 12:03:37 +00:00
<template>
2022-12-08 11:04:25 +00:00
<tr
ref="node"
:class="containerClass"
:style="node.style"
:tabindex="tabindex"
role="row"
:aria-expanded="node.children && node.children.length ? expanded : undefined"
2022-12-08 11:04:25 +00:00
:aria-level="level + 1"
:aria-setsize="ariaSetSize"
:aria-posinset="ariaPosInset"
:aria-selected="getAriaSelected"
:aria-checked="checked || undefined"
@click="onClick"
@keydown="onKeyDown"
@touchend="onTouchEnd"
2023-07-17 09:23:09 +00:00
v-bind="ptm('row', ptmOptions)"
:data-p-selected="selected"
2022-12-08 11:04:25 +00:00
>
2022-09-14 11:26:01 +00:00
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
<TTBodyCell
v-if="!columnProp(col, 'hidden')"
:column="col"
:node="node"
:level="level"
:leaf="leaf"
:indentation="indentation"
:expanded="expanded"
:selectionMode="selectionMode"
:checked="checked"
:partialChecked="partialChecked"
:templates="templates"
2022-09-14 11:26:01 +00:00
@node-toggle="$emit('node-toggle', $event)"
@checkbox-toggle="toggleCheckbox"
2023-06-05 08:36:46 +00:00
:index="i"
:loadingMode="loadingMode"
2024-01-14 14:09:03 +00:00
:unstyled="unstyled"
2023-05-11 08:11:49 +00:00
:pt="pt"
2022-09-14 11:26:01 +00:00
></TTBodyCell>
2022-09-06 12:03:37 +00:00
</template>
</tr>
<template v-if="expanded && node.children && node.children.length">
2022-09-14 11:26:01 +00:00
<TreeTableRow
v-for="childNode of node.children"
:key="nodeKey(childNode)"
:dataKey="dataKey"
2022-09-14 11:26:01 +00:00
:columns="columns"
:node="childNode"
:parentNode="node"
:level="level + 1"
:expandedKeys="expandedKeys"
:selectionMode="selectionMode"
:selectionKeys="selectionKeys"
:indentation="indentation"
2022-12-08 11:04:25 +00:00
:ariaPosInset="node.children.indexOf(childNode) + 1"
:ariaSetSize="node.children.length"
:templates="templates"
2022-09-14 11:26:01 +00:00
@node-toggle="$emit('node-toggle', $event)"
@node-click="$emit('node-click', $event)"
@checkbox-change="onCheckboxChange"
2024-01-14 14:09:03 +00:00
:unstyled="unstyled"
2023-05-11 08:11:49 +00:00
:pt="pt"
2022-09-14 11:26:01 +00:00
/>
2022-09-06 12:03:37 +00:00
</template>
</template>
<script>
2023-05-11 08:11:49 +00:00
import BaseComponent from 'primevue/basecomponent';
2022-09-14 11:26:01 +00:00
import { DomHandler, ObjectUtils } from 'primevue/utils';
2022-09-06 12:03:37 +00:00
import BodyCell from './BodyCell.vue';
export default {
name: 'TreeTableRow',
2023-07-04 06:29:36 +00:00
hostName: 'TreeTable',
2023-05-11 08:11:49 +00:00
extends: BaseComponent,
2022-09-14 11:26:01 +00:00
emits: ['node-click', 'node-toggle', 'checkbox-change', 'nodeClick', 'nodeToggle', 'checkboxChange'],
2022-09-06 12:03:37 +00:00
props: {
node: {
type: null,
default: null
},
dataKey: {
type: [String, Function],
default: 'key'
},
2022-09-06 12:03:37 +00:00
parentNode: {
type: null,
default: null
},
columns: {
type: null,
default: null
},
expandedKeys: {
type: null,
default: null
},
selectionKeys: {
type: null,
default: null
},
selectionMode: {
type: String,
default: null
},
level: {
type: Number,
default: 0
},
indentation: {
type: Number,
default: 1
2022-12-08 11:04:25 +00:00
},
tabindex: {
type: Number,
default: -1
},
ariaSetSize: {
type: Number,
default: null
},
ariaPosInset: {
type: Number,
default: null
},
loadingMode: {
type: String,
default: 'mask'
},
templates: {
type: Object,
default: null
2022-09-06 12:03:37 +00:00
}
},
nodeTouched: false,
methods: {
columnProp(col, prop) {
return ObjectUtils.getVNodeProp(col, prop);
},
toggle() {
this.$emit('node-toggle', this.node);
},
onClick(event) {
2024-05-02 10:27:11 +00:00
if (
DomHandler.isClickable(event.target) ||
DomHandler.getAttribute(event.target, 'data-pc-section') === 'rowtogglebutton' ||
DomHandler.getAttribute(event.target, 'data-pc-section') === 'rowtoggleicon' ||
event.target.tagName === 'path'
) {
2022-09-06 12:03:37 +00:00
return;
}
2022-12-08 11:04:25 +00:00
this.setTabIndexForSelectionMode(event, this.nodeTouched);
2022-09-06 12:03:37 +00:00
this.$emit('node-click', {
2022-09-14 11:26:01 +00:00
originalEvent: event,
nodeTouched: this.nodeTouched,
node: this.node
});
2022-09-06 12:03:37 +00:00
this.nodeTouched = false;
},
onTouchEnd() {
this.nodeTouched = true;
},
nodeKey(node) {
return ObjectUtils.resolveFieldData(node, this.dataKey);
},
2022-12-08 11:04:25 +00:00
onKeyDown(event, item) {
switch (event.code) {
case 'ArrowDown':
this.onArrowDownKey(event);
break;
case 'ArrowUp':
this.onArrowUpKey(event);
break;
case 'ArrowLeft':
this.onArrowLeftKey(event);
break;
case 'ArrowRight':
this.onArrowRightKey(event);
break;
case 'Home':
this.onHomeKey(event);
break;
case 'End':
this.onEndKey(event);
break;
case 'Enter':
case 'NumpadEnter':
2022-12-08 11:04:25 +00:00
case 'Space':
if (!DomHandler.isClickable(event.target)) {
this.onEnterKey(event, item);
}
2022-12-08 11:04:25 +00:00
break;
case 'Tab':
this.onTabKey(event);
break;
default:
break;
}
},
onArrowDownKey(event) {
const nextElementSibling = event.currentTarget.nextElementSibling;
nextElementSibling && this.focusRowChange(event.currentTarget, nextElementSibling);
event.preventDefault();
},
onArrowUpKey(event) {
const previousElementSibling = event.currentTarget.previousElementSibling;
previousElementSibling && this.focusRowChange(event.currentTarget, previousElementSibling);
event.preventDefault();
},
onArrowRightKey(event) {
const ishiddenIcon = DomHandler.findSingle(event.currentTarget, 'button').style.visibility === 'hidden';
2024-05-02 10:27:11 +00:00
const togglerElement = DomHandler.findSingle(this.$refs.node, '[data-pc-section="rowtogglebutton"]');
2022-12-08 11:04:25 +00:00
if (ishiddenIcon) return;
!this.expanded && togglerElement.click();
this.$nextTick(() => {
this.onArrowDownKey(event);
});
event.preventDefault();
},
onArrowLeftKey(event) {
if (this.level === 0 && !this.expanded) {
return;
}
const currentTarget = event.currentTarget;
const ishiddenIcon = DomHandler.findSingle(currentTarget, 'button').style.visibility === 'hidden';
2024-05-02 10:27:11 +00:00
const togglerElement = DomHandler.findSingle(currentTarget, '[data-pc-section="rowtogglebutton"]');
2022-12-08 11:04:25 +00:00
if (this.expanded && !ishiddenIcon) {
togglerElement.click();
return;
}
const target = this.findBeforeClickableNode(currentTarget);
target && this.focusRowChange(currentTarget, target);
},
onHomeKey(event) {
const findFirstElement = DomHandler.findSingle(event.currentTarget.parentElement, `tr[aria-level="${this.level + 1}"]`);
findFirstElement && DomHandler.focus(findFirstElement);
event.preventDefault();
},
onEndKey(event) {
const nodes = DomHandler.find(event.currentTarget.parentElement, `tr[aria-level="${this.level + 1}"]`);
const findFirstElement = nodes[nodes.length - 1];
DomHandler.focus(findFirstElement);
event.preventDefault();
},
onEnterKey(event) {
event.preventDefault();
this.setTabIndexForSelectionMode(event, this.nodeTouched);
if (this.selectionMode === 'checkbox') {
this.toggleCheckbox();
return;
}
this.$emit('node-click', {
originalEvent: event,
nodeTouched: this.nodeTouched,
node: this.node
});
this.nodeTouched = false;
},
onTabKey() {
const rows = [...DomHandler.find(this.$refs.node.parentElement, 'tr')];
const hasSelectedRow = rows.some((row) => DomHandler.getAttribute(row, 'data-p-selected') || row.getAttribute('aria-checked') === 'true');
2022-12-08 11:04:25 +00:00
rows.forEach((row) => {
row.tabIndex = -1;
});
if (hasSelectedRow) {
const selectedNodes = rows.filter((node) => DomHandler.getAttribute(node, 'data-p-selected') || node.getAttribute('aria-checked') === 'true');
2022-12-08 11:04:25 +00:00
selectedNodes[0].tabIndex = 0;
return;
}
rows[0].tabIndex = 0;
},
focusRowChange(firstFocusableRow, currentFocusedRow) {
firstFocusableRow.tabIndex = '-1';
currentFocusedRow.tabIndex = '0';
DomHandler.focus(currentFocusedRow);
},
findBeforeClickableNode(node) {
const prevNode = node.previousElementSibling;
if (prevNode) {
const prevNodeButton = prevNode.querySelector('button');
if (prevNodeButton && prevNodeButton.style.visibility !== 'hidden') {
return prevNode;
2022-09-06 12:03:37 +00:00
}
2022-12-08 11:04:25 +00:00
return this.findBeforeClickableNode(prevNode);
2022-09-06 12:03:37 +00:00
}
2022-12-08 11:04:25 +00:00
return null;
2022-09-06 12:03:37 +00:00
},
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) {
if (check) selectionKeys[this.nodeKey(node)] = { checked: true, partialChecked: false };
else delete selectionKeys[this.nodeKey(node)];
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;
2022-09-14 11:26:01 +00:00
for (let child of this.node.children) {
if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].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.nodeKey(this.node)] = { checked: true, partialChecked: false };
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
if (!check) {
delete _selectionKeys[this.nodeKey(this.node)];
2022-09-06 12:03:37 +00:00
}
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
2022-09-06 12:03:37 +00:00
}
this.$emit('checkbox-change', {
node: event.node,
check: event.check,
selectionKeys: _selectionKeys
});
},
onCheckboxChange(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;
2022-09-14 11:26:01 +00:00
for (let child of this.node.children) {
if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].checked) checkedChildCount++;
else if (_selectionKeys[this.nodeKey(child)] && _selectionKeys[this.nodeKey(child)].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.nodeKey(this.node)] = { checked: true, partialChecked: false };
2022-09-14 11:26:01 +00:00
} else {
2022-09-06 12:03:37 +00:00
if (!check) {
delete _selectionKeys[this.nodeKey(this.node)];
2022-09-06 12:03:37 +00:00
}
if (childPartialSelected || (checkedChildCount > 0 && checkedChildCount !== this.node.children.length)) _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: true };
else _selectionKeys[this.nodeKey(this.node)] = { checked: false, partialChecked: false };
2022-09-06 12:03:37 +00:00
}
this.$emit('checkbox-change', {
node: event.node,
check: event.check,
selectionKeys: _selectionKeys
});
2022-12-08 11:04:25 +00:00
},
setTabIndexForSelectionMode(event, nodeTouched) {
if (this.selectionMode !== null) {
const elements = [...DomHandler.find(this.$refs.node.parentElement, 'tr')];
event.currentTarget.tabIndex = nodeTouched === false ? -1 : 0;
if (elements.every((element) => element.tabIndex === -1)) {
elements[0].tabIndex = 0;
}
}
2022-09-06 12:03:37 +00:00
}
},
computed: {
containerClass() {
return [this.node.styleClass, this.cx('row')];
2022-09-06 12:03:37 +00:00
},
expanded() {
return this.expandedKeys && this.expandedKeys[this.nodeKey(this.node)] === true;
2022-09-06 12:03:37 +00:00
},
leaf() {
return this.node.leaf === false ? false : !(this.node.children && this.node.children.length);
},
selected() {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] === true : false;
2022-09-06 12:03:37 +00:00
},
checked() {
return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].checked : false;
2022-09-06 12:03:37 +00:00
},
partialChecked() {
return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].partialChecked : false;
2022-12-08 11:04:25 +00:00
},
getAriaSelected() {
return this.selectionMode === 'single' || this.selectionMode === 'multiple' ? this.selected : null;
2023-07-17 09:23:09 +00:00
},
ptmOptions() {
return {
context: {
selectable: this.$parentInstance.rowHover || this.$parentInstance.rowSelectionMode,
2023-07-19 12:06:27 +00:00
selected: this.selected,
scrollable: this.$parentInstance.scrollable
2023-07-17 09:23:09 +00:00
}
};
2022-09-06 12:03:37 +00:00
}
},
components: {
2022-09-14 11:26:01 +00:00
TTBodyCell: BodyCell
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>