Skip to content

Commit

Permalink
feat: add option to freeze columns to end (#3566)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan committed Mar 22, 2022
1 parent e64e89e commit 79d6444
Show file tree
Hide file tree
Showing 29 changed files with 634 additions and 42 deletions.
23 changes: 23 additions & 0 deletions packages/grid/src/vaadin-grid-column-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
'_updateVisibleChildColumns(_childColumns)',
'_childColumnsChanged(_childColumns)',
'_groupFrozenChanged(frozen, _rootColumns)',
'_groupFrozenToEndChanged(frozenToEnd, _rootColumns)',
'_groupHiddenChanged(hidden, _rootColumns)',
'_visibleChildColumnsChanged(_visibleChildColumns)',
'_colSpanChanged(_colSpan, _headerCell, _footerCell)',
Expand Down Expand Up @@ -135,6 +136,16 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
// Don’t unfreeze the frozen group because of a non-frozen child
this._lastFrozen = this._lastFrozen || value;
}

if (path === 'frozenToEnd') {
// Don’t unfreeze the frozen group because of a non-frozen child
this.frozenToEnd = this.frozenToEnd || value;
}

if (path === 'firstFrozenToEnd') {
// Don’t unfreeze the frozen group because of a non-frozen child
this._firstFrozenToEnd = this._firstFrozenToEnd || value;
}
}

/** @private */
Expand Down Expand Up @@ -239,6 +250,18 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
}
}

/** @private */
_groupFrozenToEndChanged(frozenToEnd, rootColumns) {
if (rootColumns === undefined || frozenToEnd === undefined) {
return;
}

// Don’t propagate the default `false` value.
if (frozenToEnd !== false) {
Array.from(rootColumns).forEach((col) => (col.frozenToEnd = frozenToEnd));
}
}

/** @private */
_groupHiddenChanged(hidden, rootColumns) {
if (rootColumns && !this._preventHiddenCascade) {
Expand Down
7 changes: 5 additions & 2 deletions packages/grid/src/vaadin-grid-column-reordering-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,10 @@ export const ColumnReorderingMixin = (superClass) =>
if (column1 && column2) {
const differentColumns = column1 !== column2;
const sameParent = column1.parentElement === column2.parentElement;
const sameFrozen = column1.frozen === column2.frozen;
const sameFrozen =
(column1.frozen && column2.frozen) || // both columns are frozen
(column1.frozenToEnd && column2.frozenToEnd) || // both columns are frozen to end
(!column1.frozen && !column1.frozenToEnd && !column2.frozen && !column2.frozenToEnd);
return differentColumns && sameParent && sameFrozen;
}
}
Expand Down Expand Up @@ -351,7 +354,7 @@ export const ColumnReorderingMixin = (superClass) =>
const _order = column1._order;
column1._order = column2._order;
column2._order = _order;
this._updateLastFrozen();
this._updateFrozenColumn();
this._updateFirstAndLastColumn();
}

Expand Down
31 changes: 26 additions & 5 deletions packages/grid/src/vaadin-grid-column-resizing-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const ColumnResizingMixin = (superClass) =>
.pop();
}

const eventX = e.detail.x;
const columnRowCells = Array.from(this.$.header.querySelectorAll('[part~="row"]:last-child [part~="cell"]'));
const targetCell = columnRowCells.filter((cell) => cell._column === column)[0];
// Resize the target column
Expand All @@ -66,11 +67,19 @@ export const ColumnResizingMixin = (superClass) =>
parseInt(style.borderRightWidth) +
parseInt(style.marginLeft) +
parseInt(style.marginRight);
const maxWidth =
targetCell.offsetWidth +
(this.__isRTL
? targetCell.getBoundingClientRect().left - e.detail.x
: e.detail.x - targetCell.getBoundingClientRect().right);

let maxWidth;

const cellWidth = targetCell.offsetWidth;
const cellRect = targetCell.getBoundingClientRect();

// For cells frozen to end, resize handle is flipped horizontally.
if (targetCell.hasAttribute('frozen-to-end')) {
maxWidth = cellWidth + (this.__isRTL ? eventX - cellRect.right : cellRect.left - eventX);
} else {
maxWidth = cellWidth + (this.__isRTL ? cellRect.left - eventX : eventX - cellRect.right);
}

column.width = Math.max(minWidth, maxWidth) + 'px';
column.flexGrow = 0;
}
Expand All @@ -86,6 +95,18 @@ export const ColumnResizingMixin = (superClass) =>
}
});

const cellFrozenToEnd = this._frozenToEndCells[0];

// When handle moves below the cell frozen to end, scroll into view.
if (cellFrozenToEnd && this.$.table.scrollWidth > this.$.table.offsetWidth) {
const frozenRect = cellFrozenToEnd.getBoundingClientRect();
const offset = eventX - (this.__isRTL ? frozenRect.right : frozenRect.left);

if ((this.__isRTL && offset <= 0) || (!this.__isRTL && offset >= 0)) {
this.$.table.scrollLeft += offset;
}
}

if (e.detail.state === 'end') {
this.$.scroller.toggleAttribute('column-resizing', false);
this.dispatchEvent(
Expand Down
11 changes: 11 additions & 0 deletions packages/grid/src/vaadin-grid-column.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ export declare class ColumnBaseMixinClass<TItem> {
*/
frozen: boolean;

/**
* When true, the column is frozen to end of grid.
*
* When a column inside of a column group is frozen to end, all of the sibling columns
* inside the group will get frozen to end also.
*
* Column can not be set as `frozen` and `frozenToEnd` at the same time.
* @attr {boolean} frozen-to-end
*/
frozenToEnd: boolean;

/**
* When set to true, the cells for this column are hidden.
*/
Expand Down
61 changes: 60 additions & 1 deletion packages/grid/src/vaadin-grid-column.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ export const ColumnBaseMixin = (superClass) =>
value: false
},

/**
* When true, the column is frozen to end of grid.
*
* When a column inside of a column group is frozen to end, all of the sibling columns
* inside the group will get frozen to end also.
*
* Column can not be set as `frozen` and `frozenToEnd` at the same time.
* @attr {boolean} frozen-to-end
* @type {boolean}
*/
frozenToEnd: {
type: Boolean,
value: false
},

/**
* When set to true, the cells for this column are hidden.
*/
Expand Down Expand Up @@ -79,6 +94,15 @@ export const ColumnBaseMixin = (superClass) =>
value: false
},

/**
* @type {boolean}
* @protected
*/
_firstFrozenToEnd: {
type: Boolean,
value: false
},

/** @protected */
_order: Number,

Expand Down Expand Up @@ -176,10 +200,12 @@ export const ColumnBaseMixin = (superClass) =>
return [
'_widthChanged(width, _headerCell, _footerCell, _cells.*)',
'_frozenChanged(frozen, _headerCell, _footerCell, _cells.*)',
'_frozenToEndChanged(frozenToEnd, _headerCell, _footerCell, _cells.*)',
'_flexGrowChanged(flexGrow, _headerCell, _footerCell, _cells.*)',
'_textAlignChanged(textAlign, _cells.*, _headerCell, _footerCell)',
'_orderChanged(_order, _headerCell, _footerCell, _cells.*)',
'_lastFrozenChanged(_lastFrozen)',
'_firstFrozenToEndChanged(_firstFrozenToEnd)',
'_onRendererOrBindingChanged(_renderer, _cells, _cells.*, path)',
'_onHeaderRendererOrBindingChanged(_headerRenderer, _headerCell, path, header)',
'_onFooterRendererOrBindingChanged(_footerRenderer, _footerCell)',
Expand Down Expand Up @@ -314,6 +340,23 @@ export const ColumnBaseMixin = (superClass) =>
this._grid && this._grid._frozenCellsChanged && this._grid._frozenCellsChanged();
}

/** @private */
_frozenToEndChanged(frozenToEnd) {
if (this.parentElement && this.parentElement._columnPropChanged) {
this.parentElement._columnPropChanged('frozenToEnd', frozenToEnd);
}

this._allCells.forEach((cell) => {
// Skip sizer cells to keep correct scrollWidth.
if (this._grid && cell.parentElement === this._grid.$.sizer) {
return;
}
cell.toggleAttribute('frozen-to-end', frozenToEnd);
});

this._grid && this._grid._frozenCellsChanged && this._grid._frozenCellsChanged();
}

/** @private */
_lastFrozenChanged(lastFrozen) {
this._allCells.forEach((cell) => cell.toggleAttribute('last-frozen', lastFrozen));
Expand All @@ -323,6 +366,22 @@ export const ColumnBaseMixin = (superClass) =>
}
}

/** @private */
_firstFrozenToEndChanged(firstFrozenToEnd) {
this._allCells.forEach((cell) => {
// Skip sizer cells to keep correct scrollWidth.
if (this._grid && cell.parentElement === this._grid.$.sizer) {
return;
}

cell.toggleAttribute('first-frozen-to-end', firstFrozenToEnd);
});

if (this.parentElement && this.parentElement._columnPropChanged) {
this.parentElement._firstFrozenToEnd = firstFrozenToEnd;
}
}

/**
* @param {string} path
* @return {string}
Expand Down Expand Up @@ -421,7 +480,7 @@ export const ColumnBaseMixin = (superClass) =>
}
);

this._grid._updateLastFrozen && this._grid._updateLastFrozen();
this._grid._updateFrozenColumn && this._grid._updateFrozenColumn();
this._grid._resetKeyboardNavigation && this._grid._resetKeyboardNavigation();
}
this._previousHidden = hidden;
Expand Down
6 changes: 3 additions & 3 deletions packages/grid/src/vaadin-grid-keyboard-navigation-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -854,7 +854,7 @@ export const KeyboardNavigationMixin = (superClass) =>
* @protected
*/
_scrollHorizontallyToCell(dstCell) {
if (dstCell.hasAttribute('frozen') || this.__isDetailsCell(dstCell)) {
if (dstCell.hasAttribute('frozen') || dstCell.hasAttribute('frozen-to-end') || this.__isDetailsCell(dstCell)) {
// These cells are, by design, always visible, no need to scroll.
return;
}
Expand All @@ -870,7 +870,7 @@ export const KeyboardNavigationMixin = (superClass) =>
if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
continue;
}
if (cell.hasAttribute('frozen')) {
if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
leftBoundary = cell.getBoundingClientRect().right;
break;
}
Expand All @@ -880,7 +880,7 @@ export const KeyboardNavigationMixin = (superClass) =>
if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
continue;
}
if (cell.hasAttribute('frozen')) {
if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
rightBoundary = cell.getBoundingClientRect().left;
break;
}
Expand Down
70 changes: 61 additions & 9 deletions packages/grid/src/vaadin-grid-scroll-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import { animationFrame, microTask, timeOut } from '@vaadin/component-base/src/async.js';
import { Debouncer } from '@vaadin/component-base/src/debounce.js';
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';

const timeouts = {
SCROLLING: 500
Expand All @@ -14,7 +15,7 @@ const timeouts = {
* @polymerMixin
*/
export const ScrollMixin = (superClass) =>
class ScrollMixin extends superClass {
class ScrollMixin extends ResizeMixin(superClass) {
static get properties() {
return {
/**
Expand All @@ -26,6 +27,15 @@ export const ScrollMixin = (superClass) =>
value: () => []
},

/**
* Cached array of cells frozen to end
* @private
*/
_frozenToEndCells: {
type: Array,
value: () => []
},

/** @private */
_rowWithFocusedElement: Element
};
Expand Down Expand Up @@ -67,6 +77,15 @@ export const ScrollMixin = (superClass) =>
this.$.table.addEventListener('scroll', () => this._afterScroll());
}

/**
* @protected
* @override
*/
_onResize() {
this._updateOverflow();
this.__updateHorizontalScrollPosition();
}

/**
* Scroll to a specific row index in the virtual list. Note that the row index is
* not always the same for any particular item. For example, sorting/filtering/expanding
Expand Down Expand Up @@ -177,13 +196,14 @@ export const ScrollMixin = (superClass) =>
cell.style.transform = '';
});
this._frozenCells = Array.prototype.slice.call(this.$.table.querySelectorAll('[frozen]'));
this._frozenToEndCells = Array.prototype.slice.call(this.$.table.querySelectorAll('[frozen-to-end]'));
this.__updateHorizontalScrollPosition();
});
this._updateLastFrozen();
this._updateFrozenColumn();
}

/** @protected */
_updateLastFrozen() {
_updateFrozenColumn() {
if (!this._columnTree) {
return;
}
Expand All @@ -192,26 +212,58 @@ export const ScrollMixin = (superClass) =>
columnsRow.sort((a, b) => {
return a._order - b._order;
});
const lastFrozen = columnsRow.reduce((prev, col, index) => {

let lastFrozen;
let firstFrozenToEnd;

// Use for loop to only iterate columns once
for (let i = 0; i < columnsRow.length; i++) {
const col = columnsRow[i];

col._lastFrozen = false;
return col.frozen && !col.hidden ? index : prev;
}, undefined);
col._firstFrozenToEnd = false;

if (firstFrozenToEnd === undefined && col.frozenToEnd && !col.hidden) {
firstFrozenToEnd = i;
}

if (col.frozen && !col.hidden) {
lastFrozen = i;
}
}

if (lastFrozen !== undefined) {
columnsRow[lastFrozen]._lastFrozen = true;
}

if (firstFrozenToEnd !== undefined) {
columnsRow[firstFrozenToEnd]._firstFrozenToEnd = true;
}
}

/** @private */
__updateHorizontalScrollPosition() {
const scrollWidth = this.$.table.scrollWidth;
const clientWidth = this.$.table.clientWidth;
const scrollLeft = this.__getNormalizedScrollLeft(this.$.table);

// Position cells frozen to end
const remaining = scrollLeft + clientWidth - scrollWidth;

this.$.table.style.setProperty('--_grid-horizontal-scroll-remaining', remaining + 'px');
this.$.table.style.setProperty('--_grid-horizontal-scroll-position', -this._scrollLeft + 'px');

if (this.__isRTL) {
// Translating the sticky sections using a CSS variable works nicely on LTR.
// On RTL, it causes jumpy behavior (on Desktop Safari) so we need to translate manually.
const x = this.__getNormalizedScrollLeft(this.$.table) + this.$.table.clientWidth - this.$.table.scrollWidth;
const transform = `translate(${x}px, 0)`;
const transformFrozen = `translate(${remaining}px, 0)`;
for (let i = 0; i < this._frozenCells.length; i++) {
this._frozenCells[i].style.transform = transform;
this._frozenCells[i].style.transform = transformFrozen;
}

const transformFrozenToEnd = `translate(${scrollLeft}px, 0)`;
for (let i = 0; i < this._frozenToEndCells.length; i++) {
this._frozenToEndCells[i].style.transform = transformFrozenToEnd;
}
}
}
Expand Down
Loading

0 comments on commit 79d6444

Please sign in to comment.