Refactor #5678 - TreeTable CSS and responsive structure improvements
parent
2c65e1b3fd
commit
5cb78b9b3c
|
@ -210,12 +210,6 @@ const TreeTableProps = [
|
||||||
default: 'false',
|
default: 'false',
|
||||||
description: 'When specified, enables horizontal and/or vertical scrolling.'
|
description: 'When specified, enables horizontal and/or vertical scrolling.'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'scrollDirection',
|
|
||||||
type: 'string',
|
|
||||||
default: 'vertical',
|
|
||||||
description: 'Orientation of the scrolling, options are "vertical", "horizontal" and "both".'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'scrollHeight',
|
name: 'scrollHeight',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|
|
@ -150,22 +150,26 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
scrollDirection: {
|
|
||||||
type: String,
|
|
||||||
default: 'vertical'
|
|
||||||
},
|
|
||||||
scrollHeight: {
|
scrollHeight: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
responsiveLayout: {
|
responsiveLayout: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: 'scroll'
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
tableStyle: {
|
||||||
|
type: null,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
tableClass: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
tableProps: {
|
tableProps: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
|
|
|
@ -137,7 +137,6 @@ export default {
|
||||||
selected: this.$parent.selected,
|
selected: this.$parent.selected,
|
||||||
frozen: this.columnProp('frozen'),
|
frozen: this.columnProp('frozen'),
|
||||||
scrollable: this.$parentInstance.scrollable,
|
scrollable: this.$parentInstance.scrollable,
|
||||||
scrollDirection: this.$parentInstance.scrollDirection,
|
|
||||||
showGridlines: this.$parentInstance.showGridlines,
|
showGridlines: this.$parentInstance.showGridlines,
|
||||||
size: this.$parentInstance?.size
|
size: this.$parentInstance?.size
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,6 @@ export default {
|
||||||
frozen: this.$parentInstance.scrollable && this.columnProp('frozen'),
|
frozen: this.$parentInstance.scrollable && this.columnProp('frozen'),
|
||||||
resizable: this.resizableColumns,
|
resizable: this.resizableColumns,
|
||||||
scrollable: this.$parentInstance.scrollable,
|
scrollable: this.$parentInstance.scrollable,
|
||||||
scrollDirection: this.$parentInstance.scrollDirection,
|
|
||||||
showGridlines: this.$parentInstance.showGridlines,
|
showGridlines: this.$parentInstance.showGridlines,
|
||||||
size: this.$parentInstance?.size
|
size: this.$parentInstance?.size
|
||||||
}
|
}
|
||||||
|
|
|
@ -586,20 +586,18 @@ export interface TreeTableProps {
|
||||||
* Height of the scroll viewport in fixed pixels or the 'flex' keyword for a dynamic size.
|
* Height of the scroll viewport in fixed pixels or the 'flex' keyword for a dynamic size.
|
||||||
*/
|
*/
|
||||||
scrollHeight?: HintedString<'flex'> | undefined;
|
scrollHeight?: HintedString<'flex'> | undefined;
|
||||||
/**
|
|
||||||
* Orientation of the scrolling.
|
|
||||||
* @defaultValue vertical
|
|
||||||
*/
|
|
||||||
scrollDirection?: 'vertical' | 'horizontal' | 'both' | undefined;
|
|
||||||
/**
|
|
||||||
* Defines the responsive mode, currently only option is scroll.
|
|
||||||
* @defaultValue stack
|
|
||||||
*/
|
|
||||||
responsiveLayout?: 'stack' | 'scroll' | undefined;
|
|
||||||
/**
|
/**
|
||||||
* Defines the size of the table.
|
* Defines the size of the table.
|
||||||
*/
|
*/
|
||||||
size?: 'small' | 'large' | undefined;
|
size?: 'small' | 'large' | undefined;
|
||||||
|
/**
|
||||||
|
* Inline style of the table element.
|
||||||
|
*/
|
||||||
|
tableStyle?: string | object | undefined;
|
||||||
|
/**
|
||||||
|
* Style class of the table element.
|
||||||
|
*/
|
||||||
|
tableClass?: string | object | undefined;
|
||||||
/**
|
/**
|
||||||
* Props to pass to the table element.
|
* Props to pass to the table element.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="cx('root')" data-scrollselectors=".p-treetable-scrollable-body" role="table" v-bind="ptmi('root')">
|
<div :class="cx('root')" data-scrollselectors=".p-treetable-scrollable-body" v-bind="ptmi('root')">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div v-if="loading && loadingMode === 'mask'" :class="cx('loading')" v-bind="ptm('loading')">
|
<div v-if="loading && loadingMode === 'mask'" :class="cx('loading')" v-bind="ptm('loading')">
|
||||||
<div :class="cx('mask')" v-bind="ptm('mask')">
|
<div :class="cx('mask')" v-bind="ptm('mask')">
|
||||||
|
@ -51,9 +51,9 @@
|
||||||
<slot name="paginatorrowsperpagedropdownicon" :class="slotProps.class"></slot>
|
<slot name="paginatorrowsperpagedropdownicon" :class="slotProps.class"></slot>
|
||||||
</template>
|
</template>
|
||||||
</TTPaginator>
|
</TTPaginator>
|
||||||
<div :class="cx('tableContainer')" :style="{ maxHeight: scrollHeight }" v-bind="ptm('tableContainer')">
|
<div :class="cx('tableContainer')" :style="[sx('tableContainer'), { maxHeight: scrollHeight }]" v-bind="ptm('tableContainer')">
|
||||||
<table ref="table" role="table" v-bind="{ ...tableProps, ...ptm('table') }">
|
<table ref="table" role="table" :class="[cx('table'), tableClass]" :style="tableStyle" v-bind="{ ...tableProps, ...ptm('table') }">
|
||||||
<thead :class="cx('thead')" role="rowgroup" v-bind="ptm('thead')">
|
<thead :class="cx('thead')" :style="sx('thead')" role="rowgroup" v-bind="ptm('thead')">
|
||||||
<tr role="row" v-bind="ptm('headerRow')">
|
<tr role="row" v-bind="ptm('headerRow')">
|
||||||
<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">
|
||||||
<TTHeaderCell
|
<TTHeaderCell
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot v-if="hasFooter" :class="cx('tfoot')" role="rowgroup" v-bind="ptm('tfoot')">
|
<tfoot v-if="hasFooter" :class="cx('tfoot')" :style="sx('tfoot')" role="rowgroup" v-bind="ptm('tfoot')">
|
||||||
<tr role="row" v-bind="ptm('footerRow')">
|
<tr role="row" v-bind="ptm('footerRow')">
|
||||||
<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">
|
||||||
<TTFooterCell v-if="!columnProp(col, 'hidden')" :column="col" :index="i" :unstyled="unstyled" :pt="pt"></TTFooterCell>
|
<TTFooterCell v-if="!columnProp(col, 'hidden')" :column="col" :index="i" :unstyled="unstyled" :pt="pt"></TTFooterCell>
|
||||||
|
@ -171,7 +171,7 @@
|
||||||
import { FilterService } from 'primevue/api';
|
import { FilterService } from 'primevue/api';
|
||||||
import SpinnerIcon from 'primevue/icons/spinner';
|
import SpinnerIcon from 'primevue/icons/spinner';
|
||||||
import Paginator from 'primevue/paginator';
|
import Paginator from 'primevue/paginator';
|
||||||
import { DomHandler, HelperSet, ObjectUtils } from 'primevue/utils';
|
import { DomHandler, HelperSet, ObjectUtils, UniqueComponentId } from 'primevue/utils';
|
||||||
import BaseTreeTable from './BaseTreeTable.vue';
|
import BaseTreeTable from './BaseTreeTable.vue';
|
||||||
import FooterCell from './FooterCell.vue';
|
import FooterCell from './FooterCell.vue';
|
||||||
import HeaderCell from './HeaderCell.vue';
|
import HeaderCell from './HeaderCell.vue';
|
||||||
|
@ -240,16 +240,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.scrollable && this.scrollDirection !== 'vertical') {
|
this.$el.setAttribute(this.attributeSelector, '');
|
||||||
this.updateScrollWidth();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updated() {
|
|
||||||
if (this.scrollable && this.scrollDirection !== 'vertical') {
|
|
||||||
this.updateScrollWidth();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
this.destroyStyleElement();
|
||||||
this.d_columns.clear();
|
this.d_columns.clear();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -649,21 +643,18 @@ export default {
|
||||||
let nextColumnWidth = nextColumn.offsetWidth - delta;
|
let nextColumnWidth = nextColumn.offsetWidth - delta;
|
||||||
|
|
||||||
if (newColumnWidth > 15 && nextColumnWidth > 15) {
|
if (newColumnWidth > 15 && nextColumnWidth > 15) {
|
||||||
if (!this.scrollable) {
|
|
||||||
this.resizeColumnElement.style.width = newColumnWidth + 'px';
|
|
||||||
|
|
||||||
if (nextColumn) {
|
|
||||||
nextColumn.style.width = nextColumnWidth + 'px';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.resizeTableCells(newColumnWidth, nextColumnWidth);
|
this.resizeTableCells(newColumnWidth, nextColumnWidth);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (this.columnResizeMode === 'expand') {
|
} else if (this.columnResizeMode === 'expand') {
|
||||||
this.$refs.table.style.width = this.$refs.table.offsetWidth + delta + 'px';
|
const tableWidth = this.$refs.table.offsetWidth + delta + 'px';
|
||||||
|
|
||||||
if (!this.scrollable) this.resizeColumnElement.style.width = newColumnWidth + 'px';
|
const updateTableWidth = (el) => {
|
||||||
else this.resizeTableCells(newColumnWidth);
|
!!el && (el.style.width = el.style.minWidth = tableWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reasoning: resize table cells before updating the table width so that it can use existing computed cell widths and adjust only the one column.
|
||||||
|
this.resizeTableCells(newColumnWidth);
|
||||||
|
updateTableWidth(this.$refs.table);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('column-resize-end', {
|
this.$emit('column-resize-end', {
|
||||||
|
@ -681,23 +672,31 @@ export default {
|
||||||
},
|
},
|
||||||
resizeTableCells(newColumnWidth, nextColumnWidth) {
|
resizeTableCells(newColumnWidth, nextColumnWidth) {
|
||||||
let colIndex = DomHandler.index(this.resizeColumnElement);
|
let colIndex = DomHandler.index(this.resizeColumnElement);
|
||||||
let children = this.$refs.table.children;
|
let widths = [];
|
||||||
|
let headers = DomHandler.find(this.$refs.table, 'thead[data-pc-section="thead"] > tr > th');
|
||||||
|
|
||||||
for (let child of children) {
|
headers.forEach((header) => widths.push(DomHandler.getOuterWidth(header)));
|
||||||
for (let row of child.children) {
|
|
||||||
let resizeCell = row.children[colIndex];
|
|
||||||
|
|
||||||
resizeCell.style.flex = '0 0 ' + newColumnWidth + 'px';
|
this.destroyStyleElement();
|
||||||
|
this.createStyleElement();
|
||||||
|
|
||||||
if (this.columnResizeMode === 'fit') {
|
let innerHTML = '';
|
||||||
let nextCell = resizeCell.nextElementSibling;
|
let selector = `[data-pc-name="treetable"][${this.attributeSelector}] > [data-pc-section="tablecontainer"] > table[data-pc-section="table"]`;
|
||||||
|
|
||||||
if (nextCell) {
|
widths.forEach((width, index) => {
|
||||||
nextCell.style.flex = '0 0 ' + nextColumnWidth + 'px';
|
let colWidth = index === colIndex ? newColumnWidth : nextColumnWidth && index === colIndex + 1 ? nextColumnWidth : width;
|
||||||
}
|
let style = `width: ${colWidth}px !important; max-width: ${colWidth}px !important`;
|
||||||
}
|
|
||||||
}
|
innerHTML += `
|
||||||
|
${selector} > thead[data-pc-section="thead"] > tr > th:nth-child(${index + 1}),
|
||||||
|
${selector} > tbody[data-pc-section="tbody"] > tr > td:nth-child(${index + 1}),
|
||||||
|
${selector} > tfoot[data-pc-section="tfoot"] > tr > td:nth-child(${index + 1}) {
|
||||||
|
${style}
|
||||||
}
|
}
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.styleElement.innerHTML = innerHTML;
|
||||||
},
|
},
|
||||||
bindColumnResizeEvents() {
|
bindColumnResizeEvents() {
|
||||||
if (!this.documentColumnResizeListener) {
|
if (!this.documentColumnResizeListener) {
|
||||||
|
@ -750,12 +749,21 @@ export default {
|
||||||
hasGlobalFilter() {
|
hasGlobalFilter() {
|
||||||
return this.filters && Object.prototype.hasOwnProperty.call(this.filters, 'global');
|
return this.filters && Object.prototype.hasOwnProperty.call(this.filters, 'global');
|
||||||
},
|
},
|
||||||
updateScrollWidth() {
|
|
||||||
this.$refs.table.style.width = this.$refs.table.scrollWidth + 'px';
|
|
||||||
},
|
|
||||||
getItemLabel(node) {
|
getItemLabel(node) {
|
||||||
return node.data.name;
|
return node.data.name;
|
||||||
},
|
},
|
||||||
|
createStyleElement() {
|
||||||
|
this.styleElement = document.createElement('style');
|
||||||
|
this.styleElement.type = 'text/css';
|
||||||
|
DomHandler.setAttribute(this.styleElement, 'nonce', this.$primevue?.config?.csp?.nonce);
|
||||||
|
document.head.appendChild(this.styleElement);
|
||||||
|
},
|
||||||
|
destroyStyleElement() {
|
||||||
|
if (this.styleElement) {
|
||||||
|
document.head.removeChild(this.styleElement);
|
||||||
|
this.styleElement = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
setTabindex(node, index) {
|
setTabindex(node, index) {
|
||||||
if (this.isNodeSelected(node)) {
|
if (this.isNodeSelected(node)) {
|
||||||
this.hasASelectedNode = true;
|
this.hasASelectedNode = true;
|
||||||
|
@ -852,6 +860,9 @@ export default {
|
||||||
|
|
||||||
return data ? data.length : 0;
|
return data ? data.length : 0;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
attributeSelector() {
|
||||||
|
return UniqueComponentId();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -427,6 +427,7 @@ p-treetable-gridlines .p-treetable-tbody > tr:last-child>td {
|
||||||
transition: background-color ${dt('transition.duration')}, color ${dt('transition.duration')}, border-color ${dt('transition.duration')}, box-shadow ${dt('transition.duration')}, outline-color ${dt('transition.duration')};
|
transition: background-color ${dt('transition.duration')}, color ${dt('transition.duration')}, border-color ${dt('transition.duration')}, box-shadow ${dt('transition.duration')}, outline-color ${dt('transition.duration')};
|
||||||
outline-color: transparent;
|
outline-color: transparent;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-treetable-row-toggle-button:enabled:hover {
|
.p-treetable-row-toggle-button:enabled:hover {
|
||||||
|
@ -443,6 +444,11 @@ p-treetable-gridlines .p-treetable-tbody > tr:last-child>td {
|
||||||
background: ${dt('treetable.row.action.highlight.hover.background')};
|
background: ${dt('treetable.row.action.highlight.hover.background')};
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-treetable .p-treetable-row-checkbox {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const classes = {
|
const classes = {
|
||||||
|
@ -465,12 +471,20 @@ const classes = {
|
||||||
header: 'p-treetable-header',
|
header: 'p-treetable-header',
|
||||||
paginator: ({ position }) => 'p-treetable-paginator-' + position,
|
paginator: ({ position }) => 'p-treetable-paginator-' + position,
|
||||||
tableContainer: 'p-treetable-table-container',
|
tableContainer: 'p-treetable-table-container',
|
||||||
|
table: ({ props }) => [
|
||||||
|
'p-treetable-table',
|
||||||
|
{
|
||||||
|
'p-treetable-scrollable-table': props.scrollable,
|
||||||
|
'p-treetable-resizable-table': props.resizableColumns,
|
||||||
|
'p-treetable-resizable-table-fit': props.resizableColumns && props.columnResizeMode === 'fit'
|
||||||
|
}
|
||||||
|
],
|
||||||
thead: 'p-treetable-thead',
|
thead: 'p-treetable-thead',
|
||||||
headerCell: ({ instance, props }) => [
|
headerCell: ({ instance, props, context }) => [
|
||||||
{
|
{
|
||||||
'p-treetable-sortable-column': instance.columnProp('sortable'),
|
'p-treetable-sortable-column': instance.columnProp('sortable'),
|
||||||
'p-treetable-resizable-column': props.resizableColumns,
|
'p-treetable-resizable-column': props.resizableColumns,
|
||||||
'p-treetable-column-sorted': instance.isColumnSorted(),
|
'p-treetable-column-sorted': context?.sorted,
|
||||||
'p-treetable-frozen-column': instance.columnProp('frozen')
|
'p-treetable-frozen-column': instance.columnProp('frozen')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -503,8 +517,15 @@ const classes = {
|
||||||
columnResizeHelper: 'p-treetable-column-resize-indicator'
|
columnResizeHelper: 'p-treetable-column-resize-indicator'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const inlineStyles = {
|
||||||
|
tableContainer: { overflow: 'auto' },
|
||||||
|
thead: { position: 'sticky' },
|
||||||
|
tfoot: { position: 'sticky' }
|
||||||
|
};
|
||||||
|
|
||||||
export default BaseStyle.extend({
|
export default BaseStyle.extend({
|
||||||
name: 'treetable',
|
name: 'treetable',
|
||||||
theme,
|
theme,
|
||||||
classes
|
classes,
|
||||||
|
inlineStyles
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue