Fixed #6489 - TreeTable: ContextMenu implementation

pull/6493/head
tugcekucukoglu 2024-09-27 16:30:55 +03:00
parent 0bc1689995
commit aa73025403
5 changed files with 91 additions and 8 deletions

View File

@ -30,6 +30,14 @@ export default {
type: Boolean, type: Boolean,
default: false default: false
}, },
contextMenu: {
type: Boolean,
default: false
},
contextMenuSelection: {
type: Object,
default: null
},
rows: { rows: {
type: Number, type: Number,
default: 0 default: 0

View File

@ -173,6 +173,21 @@ export interface TreeTableFilterEvent extends TreeTableSortEvent {
filteredValue: any; filteredValue: any;
} }
/**
* Custom row context menu event.
* @see {@link TreeTableEmitsOptions['row-contextmenu']}
*/
export interface TreeTableRowContextMenuEvent {
/**
* Browser event.
*/
originalEvent: Event;
/**
* Selected row data.
*/
node: TreeNode;
}
/** /**
* Custom sort metadata. * Custom sort metadata.
*/ */
@ -420,6 +435,15 @@ export interface TreeTableProps {
* @defaultValue false * @defaultValue false
*/ */
metaKeySelection?: boolean | undefined; metaKeySelection?: boolean | undefined;
/**
* Enables context menu integration.
* @defaultValue false
*/
contextMenu?: boolean | undefined;
/**
* Selected row instance with the ContextMenu.
*/
contextMenuSelection?: any | any[] | undefined;
/** /**
* Number of rows to display per page. * Number of rows to display per page.
*/ */
@ -730,6 +754,11 @@ export interface TreeTableEmitsOptions {
* @param {TreeSelectionKeys} value - New selection keys. * @param {TreeSelectionKeys} value - New selection keys.
*/ */
'update:selectionKeys'(event: TreeTableSelectionKeys): void; 'update:selectionKeys'(event: TreeTableSelectionKeys): void;
/**
* Emitted when the contextMenuSelection changes.
* @param {*} value - New value.
*/
'update:contextMenuSelection'(value: any[] | any | undefined): void;
/** /**
* Emitted when the first changes. * Emitted when the first changes.
* @param {number} value - New value. * @param {number} value - New value.
@ -795,6 +824,11 @@ export interface TreeTableEmitsOptions {
* @param {Event} event - Browser event. * @param {Event} event - Browser event.
*/ */
'column-resize-end'(event: Event): void; 'column-resize-end'(event: Event): void;
/**
* Callback to invoke when a row is selected with a ContextMenu.
* @param {TreeTableRowContextMenuEvent} event - Custom row context menu event.
*/
'row-contextmenu'(event: TreeTableRowContextMenuEvent): void;
} }
export declare type TreeTableEmits = EmitFn<TreeTableEmitsOptions>; export declare type TreeTableEmits = EmitFn<TreeTableEmitsOptions>;

View File

@ -97,10 +97,13 @@
:ariaPosInset="index + 1" :ariaPosInset="index + 1"
:tabindex="setTabindex(node, index)" :tabindex="setTabindex(node, index)"
:loadingMode="loadingMode" :loadingMode="loadingMode"
:contextMenu="contextMenu"
:contextMenuSelection="contextMenuSelection"
:templates="$slots" :templates="$slots"
@node-toggle="onNodeToggle" @node-toggle="onNodeToggle"
@node-click="onNodeClick" @node-click="onNodeClick"
@checkbox-change="onCheckboxChange" @checkbox-change="onCheckboxChange"
@row-rightclick="onRowRightClick($event)"
:unstyled="unstyled" :unstyled="unstyled"
:pt="pt" :pt="pt"
></TTRow> ></TTRow>
@ -198,7 +201,9 @@ export default {
'update:multiSortMeta', 'update:multiSortMeta',
'sort', 'sort',
'filter', 'filter',
'column-resize-end' 'column-resize-end',
'update:contextMenuSelection',
'row-contextmenu'
], ],
provide() { provide() {
return { return {
@ -348,6 +353,15 @@ export default {
if (event.check) this.$emit('node-select', event.node); if (event.check) this.$emit('node-select', event.node);
else this.$emit('node-unselect', event.node); else this.$emit('node-unselect', event.node);
}, },
onRowRightClick(event) {
if (this.contextMenu) {
clearSelection();
event.originalEvent.target.focus();
}
this.$emit('update:contextMenuSelection', event.node);
this.$emit('row-contextmenu', event);
},
isSingleSelectionMode() { isSingleSelectionMode() {
return this.selectionMode === 'single'; return this.selectionMode === 'single';
}, },

View File

@ -14,8 +14,10 @@
@click="onClick" @click="onClick"
@keydown="onKeyDown" @keydown="onKeyDown"
@touchend="onTouchEnd" @touchend="onTouchEnd"
@contextmenu="onRowRightClick"
v-bind="ptm('row', ptmOptions)" v-bind="ptm('row', ptmOptions)"
:data-p-selected="selected" :data-p-selected="selected"
:data-p-selected-contextmenu="contextMenuSelection && isSelectedWithContextMenu"
> >
<template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i"> <template v-for="(col, i) of columns" :key="columnProp(col, 'columnKey') || columnProp(col, 'field') || i">
<TTBodyCell <TTBodyCell
@ -51,12 +53,15 @@
:expandedKeys="expandedKeys" :expandedKeys="expandedKeys"
:selectionMode="selectionMode" :selectionMode="selectionMode"
:selectionKeys="selectionKeys" :selectionKeys="selectionKeys"
:contextMenu="contextMenu"
:contextMenuSelection="contextMenuSelection"
:indentation="indentation" :indentation="indentation"
:ariaPosInset="node.children.indexOf(childNode) + 1" :ariaPosInset="node.children.indexOf(childNode) + 1"
:ariaSetSize="node.children.length" :ariaSetSize="node.children.length"
:templates="templates" :templates="templates"
@node-toggle="$emit('node-toggle', $event)" @node-toggle="$emit('node-toggle', $event)"
@node-click="$emit('node-click', $event)" @node-click="$emit('node-click', $event)"
@row-rightclick="$emit('row-rightclick', $event)"
@checkbox-change="onCheckboxChange" @checkbox-change="onCheckboxChange"
:unstyled="unstyled" :unstyled="unstyled"
:pt="pt" :pt="pt"
@ -66,7 +71,7 @@
<script> <script>
import { find, findSingle, focus, getAttribute, isClickable } from '@primeuix/utils/dom'; import { find, findSingle, focus, getAttribute, isClickable } from '@primeuix/utils/dom';
import { resolveFieldData } from '@primeuix/utils/object'; import { equals, resolveFieldData } from '@primeuix/utils/object';
import BaseComponent from '@primevue/core/basecomponent'; import BaseComponent from '@primevue/core/basecomponent';
import { getVNodeProp } from '@primevue/core/utils'; import { getVNodeProp } from '@primevue/core/utils';
import BodyCell from './BodyCell.vue'; import BodyCell from './BodyCell.vue';
@ -75,7 +80,7 @@ export default {
name: 'TreeTableRow', name: 'TreeTableRow',
hostName: 'TreeTable', hostName: 'TreeTable',
extends: BaseComponent, extends: BaseComponent,
emits: ['node-click', 'node-toggle', 'checkbox-change', 'nodeClick', 'nodeToggle', 'checkboxChange'], emits: ['node-click', 'node-toggle', 'checkbox-change', 'nodeClick', 'nodeToggle', 'checkboxChange', 'row-rightclick', 'rowRightclick'],
props: { props: {
node: { node: {
type: null, type: null,
@ -132,6 +137,14 @@ export default {
templates: { templates: {
type: Object, type: Object,
default: null default: null
},
contextMenu: {
type: Boolean,
default: false
},
contextMenuSelection: {
type: Object,
default: null
} }
}, },
nodeTouched: false, nodeTouched: false,
@ -156,6 +169,12 @@ export default {
}); });
this.nodeTouched = false; this.nodeTouched = false;
}, },
onRowRightClick(event) {
this.$emit('row-rightclick', {
originalEvent: event,
node: this.node
});
},
onTouchEnd() { onTouchEnd() {
this.nodeTouched = true; this.nodeTouched = true;
}, },
@ -426,6 +445,13 @@ export default {
selected() { selected() {
return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] === true : false; return this.selectionMode && this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] === true : false;
}, },
isSelectedWithContextMenu() {
if (this.node && this.contextMenuSelection) {
return equals(this.node, this.contextMenuSelection, this.dataKey);
}
return false;
},
checked() { checked() {
return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].checked : false; return this.selectionKeys ? this.selectionKeys[this.nodeKey(this.node)] && this.selectionKeys[this.nodeKey(this.node)].checked : false;
}, },

View File

@ -198,9 +198,9 @@ const theme = ({ dt }) => `
.p-treetable-tbody > tr:focus-visible, .p-treetable-tbody > tr:focus-visible,
.p-treetable-tbody > tr.p-treetable-contextmenu-row-selected { .p-treetable-tbody > tr.p-treetable-contextmenu-row-selected {
box-shadow: ${dt('treetable.body.cell.focus.ring.shadow')}; box-shadow: ${dt('treetable.row.focus.ring.shadow')};
outline: ${dt('treetable.body.cell.focus.ring.width')} ${dt('treetable.body.cell.focus.ring.style')} ${dt('treetable.body.cell.focus.ring.color')}; outline: ${dt('treetable.row.focus.ring.width')} ${dt('treetable.row.focus.ring.style')} ${dt('treetable.row.focus.ring.color')};
outline-offset: ${dt('treetable.body.cell.focus.ring.offset')}; outline-offset: ${dt('treetable.row.focus.ring.offset')};
} }
.p-treetable-tfoot > tr > td { .p-treetable-tfoot > tr > td {
@ -470,9 +470,10 @@ const classes = {
sortIcon: 'p-treetable-sort-icon', sortIcon: 'p-treetable-sort-icon',
pcSortBadge: 'p-treetable-sort-badge', pcSortBadge: 'p-treetable-sort-badge',
tbody: 'p-treetable-tbody', tbody: 'p-treetable-tbody',
row: ({ instance }) => [ row: ({ props, instance }) => [
{ {
'p-treetable-row-selected': instance.selected 'p-treetable-row-selected': instance.selected,
'p-treetable-contextmenu-row-selected': props.contextMenuSelection && instance.isSelectedWithContextMenu
} }
], ],
bodyCell: ({ instance }) => [ bodyCell: ({ instance }) => [