@vaadin/grid 23.0.6 → 23.1.0-alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/grid",
3
- "version": "23.0.6",
3
+ "version": "23.1.0-alpha3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -41,19 +41,19 @@
41
41
  "dependencies": {
42
42
  "@open-wc/dedupe-mixin": "^1.3.0",
43
43
  "@polymer/polymer": "^3.0.0",
44
- "@vaadin/checkbox": "^23.0.6",
45
- "@vaadin/component-base": "^23.0.6",
46
- "@vaadin/text-field": "^23.0.6",
47
- "@vaadin/vaadin-lumo-styles": "^23.0.6",
48
- "@vaadin/vaadin-material-styles": "^23.0.6",
49
- "@vaadin/vaadin-themable-mixin": "^23.0.6"
44
+ "@vaadin/checkbox": "23.1.0-alpha3",
45
+ "@vaadin/component-base": "23.1.0-alpha3",
46
+ "@vaadin/text-field": "23.1.0-alpha3",
47
+ "@vaadin/vaadin-lumo-styles": "23.1.0-alpha3",
48
+ "@vaadin/vaadin-material-styles": "23.1.0-alpha3",
49
+ "@vaadin/vaadin-themable-mixin": "23.1.0-alpha3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@esm-bundle/chai": "^4.3.4",
53
- "@vaadin/polymer-legacy-adapter": "^23.0.6",
53
+ "@vaadin/polymer-legacy-adapter": "23.1.0-alpha3",
54
54
  "@vaadin/testing-helpers": "^0.3.2",
55
55
  "lit": "^2.0.0",
56
- "sinon": "^9.2.0"
56
+ "sinon": "^13.0.2"
57
57
  },
58
- "gitHead": "82ca8522e24a63343fb28bcb4c686e55d25c8858"
58
+ "gitHead": "8c9e64e8dfa158dd52a9bf6da351ff038c88ca85"
59
59
  }
@@ -88,6 +88,7 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
88
88
  '_updateVisibleChildColumns(_childColumns)',
89
89
  '_childColumnsChanged(_childColumns)',
90
90
  '_groupFrozenChanged(frozen, _rootColumns)',
91
+ '_groupFrozenToEndChanged(frozenToEnd, _rootColumns)',
91
92
  '_groupHiddenChanged(hidden, _rootColumns)',
92
93
  '_visibleChildColumnsChanged(_visibleChildColumns)',
93
94
  '_colSpanChanged(_colSpan, _headerCell, _footerCell)',
@@ -135,6 +136,16 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
135
136
  // Don’t unfreeze the frozen group because of a non-frozen child
136
137
  this._lastFrozen = this._lastFrozen || value;
137
138
  }
139
+
140
+ if (path === 'frozenToEnd') {
141
+ // Don’t unfreeze the frozen group because of a non-frozen child
142
+ this.frozenToEnd = this.frozenToEnd || value;
143
+ }
144
+
145
+ if (path === 'firstFrozenToEnd') {
146
+ // Don’t unfreeze the frozen group because of a non-frozen child
147
+ this._firstFrozenToEnd = this._firstFrozenToEnd || value;
148
+ }
138
149
  }
139
150
 
140
151
  /** @private */
@@ -239,6 +250,18 @@ class GridColumnGroup extends ColumnBaseMixin(PolymerElement) {
239
250
  }
240
251
  }
241
252
 
253
+ /** @private */
254
+ _groupFrozenToEndChanged(frozenToEnd, rootColumns) {
255
+ if (rootColumns === undefined || frozenToEnd === undefined) {
256
+ return;
257
+ }
258
+
259
+ // Don’t propagate the default `false` value.
260
+ if (frozenToEnd !== false) {
261
+ Array.from(rootColumns).forEach((col) => (col.frozenToEnd = frozenToEnd));
262
+ }
263
+ }
264
+
242
265
  /** @private */
243
266
  _groupHiddenChanged(hidden, rootColumns) {
244
267
  if (rootColumns && !this._preventHiddenCascade) {
@@ -317,7 +317,10 @@ export const ColumnReorderingMixin = (superClass) =>
317
317
  if (column1 && column2) {
318
318
  const differentColumns = column1 !== column2;
319
319
  const sameParent = column1.parentElement === column2.parentElement;
320
- const sameFrozen = column1.frozen === column2.frozen;
320
+ const sameFrozen =
321
+ (column1.frozen && column2.frozen) || // both columns are frozen
322
+ (column1.frozenToEnd && column2.frozenToEnd) || // both columns are frozen to end
323
+ (!column1.frozen && !column1.frozenToEnd && !column2.frozen && !column2.frozenToEnd);
321
324
  return differentColumns && sameParent && sameFrozen;
322
325
  }
323
326
  }
@@ -351,7 +354,7 @@ export const ColumnReorderingMixin = (superClass) =>
351
354
  const _order = column1._order;
352
355
  column1._order = column2._order;
353
356
  column2._order = _order;
354
- this._updateLastFrozen();
357
+ this._updateFrozenColumn();
355
358
  this._updateFirstAndLastColumn();
356
359
  }
357
360
 
@@ -53,6 +53,7 @@ export const ColumnResizingMixin = (superClass) =>
53
53
  .pop();
54
54
  }
55
55
 
56
+ const eventX = e.detail.x;
56
57
  const columnRowCells = Array.from(this.$.header.querySelectorAll('[part~="row"]:last-child [part~="cell"]'));
57
58
  const targetCell = columnRowCells.filter((cell) => cell._column === column)[0];
58
59
  // Resize the target column
@@ -66,11 +67,19 @@ export const ColumnResizingMixin = (superClass) =>
66
67
  parseInt(style.borderRightWidth) +
67
68
  parseInt(style.marginLeft) +
68
69
  parseInt(style.marginRight);
69
- const maxWidth =
70
- targetCell.offsetWidth +
71
- (this.__isRTL
72
- ? targetCell.getBoundingClientRect().left - e.detail.x
73
- : e.detail.x - targetCell.getBoundingClientRect().right);
70
+
71
+ let maxWidth;
72
+
73
+ const cellWidth = targetCell.offsetWidth;
74
+ const cellRect = targetCell.getBoundingClientRect();
75
+
76
+ // For cells frozen to end, resize handle is flipped horizontally.
77
+ if (targetCell.hasAttribute('frozen-to-end')) {
78
+ maxWidth = cellWidth + (this.__isRTL ? eventX - cellRect.right : cellRect.left - eventX);
79
+ } else {
80
+ maxWidth = cellWidth + (this.__isRTL ? cellRect.left - eventX : eventX - cellRect.right);
81
+ }
82
+
74
83
  column.width = Math.max(minWidth, maxWidth) + 'px';
75
84
  column.flexGrow = 0;
76
85
  }
@@ -86,6 +95,18 @@ export const ColumnResizingMixin = (superClass) =>
86
95
  }
87
96
  });
88
97
 
98
+ const cellFrozenToEnd = this._frozenToEndCells[0];
99
+
100
+ // When handle moves below the cell frozen to end, scroll into view.
101
+ if (cellFrozenToEnd && this.$.table.scrollWidth > this.$.table.offsetWidth) {
102
+ const frozenRect = cellFrozenToEnd.getBoundingClientRect();
103
+ const offset = eventX - (this.__isRTL ? frozenRect.right : frozenRect.left);
104
+
105
+ if ((this.__isRTL && offset <= 0) || (!this.__isRTL && offset >= 0)) {
106
+ this.$.table.scrollLeft += offset;
107
+ }
108
+ }
109
+
89
110
  if (e.detail.state === 'end') {
90
111
  this.$.scroller.toggleAttribute('column-resizing', false);
91
112
  this.dispatchEvent(
@@ -32,6 +32,17 @@ export declare class ColumnBaseMixinClass<TItem> {
32
32
  */
33
33
  frozen: boolean;
34
34
 
35
+ /**
36
+ * When true, the column is frozen to end of grid.
37
+ *
38
+ * When a column inside of a column group is frozen to end, all of the sibling columns
39
+ * inside the group will get frozen to end also.
40
+ *
41
+ * Column can not be set as `frozen` and `frozenToEnd` at the same time.
42
+ * @attr {boolean} frozen-to-end
43
+ */
44
+ frozenToEnd: boolean;
45
+
35
46
  /**
36
47
  * When set to true, the cells for this column are hidden.
37
48
  */
@@ -45,6 +45,21 @@ export const ColumnBaseMixin = (superClass) =>
45
45
  value: false
46
46
  },
47
47
 
48
+ /**
49
+ * When true, the column is frozen to end of grid.
50
+ *
51
+ * When a column inside of a column group is frozen to end, all of the sibling columns
52
+ * inside the group will get frozen to end also.
53
+ *
54
+ * Column can not be set as `frozen` and `frozenToEnd` at the same time.
55
+ * @attr {boolean} frozen-to-end
56
+ * @type {boolean}
57
+ */
58
+ frozenToEnd: {
59
+ type: Boolean,
60
+ value: false
61
+ },
62
+
48
63
  /**
49
64
  * When set to true, the cells for this column are hidden.
50
65
  */
@@ -79,6 +94,15 @@ export const ColumnBaseMixin = (superClass) =>
79
94
  value: false
80
95
  },
81
96
 
97
+ /**
98
+ * @type {boolean}
99
+ * @protected
100
+ */
101
+ _firstFrozenToEnd: {
102
+ type: Boolean,
103
+ value: false
104
+ },
105
+
82
106
  /** @protected */
83
107
  _order: Number,
84
108
 
@@ -176,10 +200,12 @@ export const ColumnBaseMixin = (superClass) =>
176
200
  return [
177
201
  '_widthChanged(width, _headerCell, _footerCell, _cells.*)',
178
202
  '_frozenChanged(frozen, _headerCell, _footerCell, _cells.*)',
203
+ '_frozenToEndChanged(frozenToEnd, _headerCell, _footerCell, _cells.*)',
179
204
  '_flexGrowChanged(flexGrow, _headerCell, _footerCell, _cells.*)',
180
205
  '_textAlignChanged(textAlign, _cells.*, _headerCell, _footerCell)',
181
206
  '_orderChanged(_order, _headerCell, _footerCell, _cells.*)',
182
207
  '_lastFrozenChanged(_lastFrozen)',
208
+ '_firstFrozenToEndChanged(_firstFrozenToEnd)',
183
209
  '_onRendererOrBindingChanged(_renderer, _cells, _cells.*, path)',
184
210
  '_onHeaderRendererOrBindingChanged(_headerRenderer, _headerCell, path, header)',
185
211
  '_onFooterRendererOrBindingChanged(_footerRenderer, _footerCell)',
@@ -314,6 +340,23 @@ export const ColumnBaseMixin = (superClass) =>
314
340
  this._grid && this._grid._frozenCellsChanged && this._grid._frozenCellsChanged();
315
341
  }
316
342
 
343
+ /** @private */
344
+ _frozenToEndChanged(frozenToEnd) {
345
+ if (this.parentElement && this.parentElement._columnPropChanged) {
346
+ this.parentElement._columnPropChanged('frozenToEnd', frozenToEnd);
347
+ }
348
+
349
+ this._allCells.forEach((cell) => {
350
+ // Skip sizer cells to keep correct scrollWidth.
351
+ if (this._grid && cell.parentElement === this._grid.$.sizer) {
352
+ return;
353
+ }
354
+ cell.toggleAttribute('frozen-to-end', frozenToEnd);
355
+ });
356
+
357
+ this._grid && this._grid._frozenCellsChanged && this._grid._frozenCellsChanged();
358
+ }
359
+
317
360
  /** @private */
318
361
  _lastFrozenChanged(lastFrozen) {
319
362
  this._allCells.forEach((cell) => cell.toggleAttribute('last-frozen', lastFrozen));
@@ -323,6 +366,22 @@ export const ColumnBaseMixin = (superClass) =>
323
366
  }
324
367
  }
325
368
 
369
+ /** @private */
370
+ _firstFrozenToEndChanged(firstFrozenToEnd) {
371
+ this._allCells.forEach((cell) => {
372
+ // Skip sizer cells to keep correct scrollWidth.
373
+ if (this._grid && cell.parentElement === this._grid.$.sizer) {
374
+ return;
375
+ }
376
+
377
+ cell.toggleAttribute('first-frozen-to-end', firstFrozenToEnd);
378
+ });
379
+
380
+ if (this.parentElement && this.parentElement._columnPropChanged) {
381
+ this.parentElement._firstFrozenToEnd = firstFrozenToEnd;
382
+ }
383
+ }
384
+
326
385
  /**
327
386
  * @param {string} path
328
387
  * @return {string}
@@ -421,7 +480,7 @@ export const ColumnBaseMixin = (superClass) =>
421
480
  }
422
481
  );
423
482
 
424
- this._grid._updateLastFrozen && this._grid._updateLastFrozen();
483
+ this._grid._updateFrozenColumn && this._grid._updateFrozenColumn();
425
484
  this._grid._resetKeyboardNavigation && this._grid._resetKeyboardNavigation();
426
485
  }
427
486
  this._previousHidden = hidden;
@@ -751,6 +751,7 @@ export const KeyboardNavigationMixin = (superClass) =>
751
751
  _detectInteracting(e) {
752
752
  const isInteracting = e.composedPath().some((el) => el.localName === 'vaadin-grid-cell-content');
753
753
  this._setInteracting(isInteracting);
754
+ this.__updateHorizontalScrollPosition();
754
755
  }
755
756
 
756
757
  /** @private */
@@ -854,7 +855,7 @@ export const KeyboardNavigationMixin = (superClass) =>
854
855
  * @protected
855
856
  */
856
857
  _scrollHorizontallyToCell(dstCell) {
857
- if (dstCell.hasAttribute('frozen') || this.__isDetailsCell(dstCell)) {
858
+ if (dstCell.hasAttribute('frozen') || dstCell.hasAttribute('frozen-to-end') || this.__isDetailsCell(dstCell)) {
858
859
  // These cells are, by design, always visible, no need to scroll.
859
860
  return;
860
861
  }
@@ -870,7 +871,7 @@ export const KeyboardNavigationMixin = (superClass) =>
870
871
  if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
871
872
  continue;
872
873
  }
873
- if (cell.hasAttribute('frozen')) {
874
+ if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
874
875
  leftBoundary = cell.getBoundingClientRect().right;
875
876
  break;
876
877
  }
@@ -880,7 +881,7 @@ export const KeyboardNavigationMixin = (superClass) =>
880
881
  if (cell.hasAttribute('hidden') || this.__isDetailsCell(cell)) {
881
882
  continue;
882
883
  }
883
- if (cell.hasAttribute('frozen')) {
884
+ if (cell.hasAttribute('frozen') || cell.hasAttribute('frozen-to-end')) {
884
885
  rightBoundary = cell.getBoundingClientRect().left;
885
886
  break;
886
887
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { animationFrame, microTask, timeOut } from '@vaadin/component-base/src/async.js';
7
7
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
8
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
8
9
 
9
10
  const timeouts = {
10
11
  SCROLLING: 500
@@ -14,7 +15,7 @@ const timeouts = {
14
15
  * @polymerMixin
15
16
  */
16
17
  export const ScrollMixin = (superClass) =>
17
- class ScrollMixin extends superClass {
18
+ class ScrollMixin extends ResizeMixin(superClass) {
18
19
  static get properties() {
19
20
  return {
20
21
  /**
@@ -26,6 +27,15 @@ export const ScrollMixin = (superClass) =>
26
27
  value: () => []
27
28
  },
28
29
 
30
+ /**
31
+ * Cached array of cells frozen to end
32
+ * @private
33
+ */
34
+ _frozenToEndCells: {
35
+ type: Array,
36
+ value: () => []
37
+ },
38
+
29
39
  /** @private */
30
40
  _rowWithFocusedElement: Element
31
41
  };
@@ -67,6 +77,15 @@ export const ScrollMixin = (superClass) =>
67
77
  this.$.table.addEventListener('scroll', () => this._afterScroll());
68
78
  }
69
79
 
80
+ /**
81
+ * @protected
82
+ * @override
83
+ */
84
+ _onResize() {
85
+ this._updateOverflow();
86
+ this.__updateHorizontalScrollPosition();
87
+ }
88
+
70
89
  /**
71
90
  * Scroll to a specific row index in the virtual list. Note that the row index is
72
91
  * not always the same for any particular item. For example, sorting/filtering/expanding
@@ -136,6 +155,22 @@ export const ScrollMixin = (superClass) =>
136
155
  overflow += ' top';
137
156
  }
138
157
 
158
+ const scrollLeft = this.__getNormalizedScrollLeft(table);
159
+ if (scrollLeft > 0) {
160
+ overflow += ' start';
161
+ }
162
+
163
+ if (scrollLeft < table.scrollWidth - table.clientWidth) {
164
+ overflow += ' end';
165
+ }
166
+
167
+ if (this.__isRTL) {
168
+ overflow = overflow.replace(/start|end/gi, (matched) => {
169
+ return matched === 'start' ? 'end' : 'start';
170
+ });
171
+ }
172
+
173
+ // TODO: Remove "right" and "left" values in the next major.
139
174
  if (table.scrollLeft < table.scrollWidth - table.clientWidth) {
140
175
  overflow += ' right';
141
176
  }
@@ -161,13 +196,14 @@ export const ScrollMixin = (superClass) =>
161
196
  cell.style.transform = '';
162
197
  });
163
198
  this._frozenCells = Array.prototype.slice.call(this.$.table.querySelectorAll('[frozen]'));
199
+ this._frozenToEndCells = Array.prototype.slice.call(this.$.table.querySelectorAll('[frozen-to-end]'));
164
200
  this.__updateHorizontalScrollPosition();
165
201
  });
166
- this._updateLastFrozen();
202
+ this._updateFrozenColumn();
167
203
  }
168
204
 
169
205
  /** @protected */
170
- _updateLastFrozen() {
206
+ _updateFrozenColumn() {
171
207
  if (!this._columnTree) {
172
208
  return;
173
209
  }
@@ -176,27 +212,66 @@ export const ScrollMixin = (superClass) =>
176
212
  columnsRow.sort((a, b) => {
177
213
  return a._order - b._order;
178
214
  });
179
- const lastFrozen = columnsRow.reduce((prev, col, index) => {
215
+
216
+ let lastFrozen;
217
+ let firstFrozenToEnd;
218
+
219
+ // Use for loop to only iterate columns once
220
+ for (let i = 0; i < columnsRow.length; i++) {
221
+ const col = columnsRow[i];
222
+
180
223
  col._lastFrozen = false;
181
- return col.frozen && !col.hidden ? index : prev;
182
- }, undefined);
224
+ col._firstFrozenToEnd = false;
225
+
226
+ if (firstFrozenToEnd === undefined && col.frozenToEnd && !col.hidden) {
227
+ firstFrozenToEnd = i;
228
+ }
229
+
230
+ if (col.frozen && !col.hidden) {
231
+ lastFrozen = i;
232
+ }
233
+ }
234
+
183
235
  if (lastFrozen !== undefined) {
184
236
  columnsRow[lastFrozen]._lastFrozen = true;
185
237
  }
238
+
239
+ if (firstFrozenToEnd !== undefined) {
240
+ columnsRow[firstFrozenToEnd]._firstFrozenToEnd = true;
241
+ }
186
242
  }
187
243
 
188
244
  /** @private */
189
245
  __updateHorizontalScrollPosition() {
190
- this.$.table.style.setProperty('--_grid-horizontal-scroll-position', -this._scrollLeft + 'px');
246
+ const scrollWidth = this.$.table.scrollWidth;
247
+ const clientWidth = this.$.table.clientWidth;
248
+ const scrollLeft = Math.max(0, this.$.table.scrollLeft);
249
+ const normalizedScrollLeft = this.__getNormalizedScrollLeft(this.$.table);
191
250
 
192
- if (this.__isRTL) {
193
- // Translating the sticky sections using a CSS variable works nicely on LTR.
194
- // On RTL, it causes jumpy behavior (on Desktop Safari) so we need to translate manually.
195
- const x = this.__getNormalizedScrollLeft(this.$.table) + this.$.table.clientWidth - this.$.table.scrollWidth;
196
- const transform = `translate(${x}px, 0)`;
197
- for (let i = 0; i < this._frozenCells.length; i++) {
198
- this._frozenCells[i].style.transform = transform;
199
- }
251
+ // Position header, footer and items container
252
+ const transform = `translate(${-scrollLeft}px, 0)`;
253
+ this.$.header.style.transform = transform;
254
+ this.$.footer.style.transform = transform;
255
+ this.$.items.style.transform = transform;
256
+
257
+ // Position frozen cells
258
+ const x = this.__isRTL ? normalizedScrollLeft + clientWidth - scrollWidth : scrollLeft;
259
+ const transformFrozen = `translate(${x}px, 0)`;
260
+ for (let i = 0; i < this._frozenCells.length; i++) {
261
+ this._frozenCells[i].style.transform = transformFrozen;
262
+ }
263
+
264
+ // Position cells frozen to end
265
+ const remaining = this.__isRTL ? normalizedScrollLeft : scrollLeft + clientWidth - scrollWidth;
266
+ const transformFrozenToEnd = `translate(${remaining}px, 0)`;
267
+ for (let i = 0; i < this._frozenToEndCells.length; i++) {
268
+ this._frozenToEndCells[i].style.transform = transformFrozenToEnd;
269
+ }
270
+
271
+ // Only update the --_grid-horizontal-scroll-position custom property when navigating
272
+ // on row focus mode to avoid performance issues.
273
+ if (this.hasAttribute('navigating') && this.__rowFocusMode) {
274
+ this.$.table.style.setProperty('--_grid-horizontal-scroll-position', -x + 'px');
200
275
  }
201
276
  }
202
277
  };
@@ -74,7 +74,6 @@ registerStyles(
74
74
 
75
75
  #header,
76
76
  #footer {
77
- transform: translateX(var(--_grid-horizontal-scroll-position));
78
77
  display: block;
79
78
  position: -webkit-sticky;
80
79
  position: sticky;
@@ -102,7 +101,6 @@ registerStyles(
102
101
  }
103
102
 
104
103
  #items {
105
- transform: translateX(var(--_grid-horizontal-scroll-position));
106
104
  flex-grow: 1;
107
105
  flex-shrink: 0;
108
106
  display: block;
@@ -164,9 +162,9 @@ registerStyles(
164
162
  display: none !important;
165
163
  }
166
164
 
167
- [frozen] {
165
+ [frozen],
166
+ [frozen-to-end] {
168
167
  z-index: 2;
169
- transform: translateX(calc(-1 * var(--_grid-horizontal-scroll-position)));
170
168
  will-change: transform;
171
169
  }
172
170
 
@@ -224,6 +222,26 @@ registerStyles(
224
222
  right: 0;
225
223
  }
226
224
 
225
+ [frozen-to-end] [part~='resize-handle'] {
226
+ left: 0;
227
+ right: auto;
228
+ }
229
+
230
+ [frozen-to-end] [part~='resize-handle']::before {
231
+ left: 0;
232
+ right: auto;
233
+ }
234
+
235
+ [first-frozen-to-end] [part~='resize-handle']::before {
236
+ width: 18px;
237
+ transform: none;
238
+ }
239
+
240
+ /* Hide resize handle if scrolled to end */
241
+ :host(:not([overflow~='end'])) [first-frozen-to-end] [part~='resize-handle'] {
242
+ display: none;
243
+ }
244
+
227
245
  #scroller[column-resizing] {
228
246
  -ms-user-select: none;
229
247
  -moz-user-select: none;
@@ -267,10 +285,6 @@ registerStyles(
267
285
 
268
286
  /* RTL specific styles */
269
287
 
270
- :host([dir='rtl']) *:is(#items, #header, #footer, [frozen]) {
271
- transform: none;
272
- }
273
-
274
288
  :host([dir='rtl']) #items,
275
289
  :host([dir='rtl']) #header,
276
290
  :host([dir='rtl']) #footer {
@@ -296,6 +310,16 @@ registerStyles(
296
310
  left: 0;
297
311
  right: auto;
298
312
  }
313
+
314
+ :host([dir='rtl']) [frozen-to-end] [part~='resize-handle'] {
315
+ right: 0;
316
+ left: auto;
317
+ }
318
+
319
+ :host([dir='rtl']) [frozen-to-end] [part~='resize-handle']::before {
320
+ right: 0;
321
+ left: auto;
322
+ }
299
323
  `,
300
324
  { moduleId: 'vaadin-grid-styles' }
301
325
  );
@@ -302,7 +302,7 @@ export interface GridEventMap<TItem> extends HTMLElementEventMap, GridCustomEven
302
302
  * `loading` | Set when the grid is loading data from data provider | :host
303
303
  * `interacting` | Keyboard navigation in interaction mode | :host
304
304
  * `navigating` | Keyboard navigation in navigation mode | :host
305
- * `overflow` | Set when rows are overflowing the grid viewport. Possible values: `top`, `bottom`, `left`, `right` | :host
305
+ * `overflow` | Set when rows are overflowing the grid viewport. Possible values: `top`, `bottom`, `start`, `end` | :host
306
306
  * `reordering` | Set when the grid's columns are being reordered | :host
307
307
  * `dragover` | Set when the grid (not a specific row) is dragged over | :host
308
308
  * `dragging-rows` : Set when grid rows are dragged | :host
@@ -188,7 +188,7 @@ import { StylingMixin } from './vaadin-grid-styling-mixin.js';
188
188
  * `loading` | Set when the grid is loading data from data provider | :host
189
189
  * `interacting` | Keyboard navigation in interaction mode | :host
190
190
  * `navigating` | Keyboard navigation in navigation mode | :host
191
- * `overflow` | Set when rows are overflowing the grid viewport. Possible values: `top`, `bottom`, `left`, `right` | :host
191
+ * `overflow` | Set when rows are overflowing the grid viewport. Possible values: `top`, `bottom`, `start`, `end` | :host
192
192
  * `reordering` | Set when the grid's columns are being reordered | :host
193
193
  * `dragover` | Set when the grid (not a specific row) is dragged over | :host
194
194
  * `dragging-rows` : Set when grid rows are dragged | :host
@@ -940,6 +940,7 @@ class Grid extends ElementMixin(
940
940
  _resizeHandler() {
941
941
  this._updateDetailsCellHeights();
942
942
  this.__updateFooterPositioning();
943
+ this.__updateHorizontalScrollPosition();
943
944
  }
944
945
 
945
946
  /** @private */
@@ -299,10 +299,18 @@ registerStyles(
299
299
  overflow: hidden;
300
300
  }
301
301
 
302
- :host([overflow~='left']) [part~='cell'][last-frozen]:not([part~='details-cell']) {
302
+ :host([overflow~='start']) [part~='cell'][last-frozen]:not([part~='details-cell']) {
303
303
  border-right-color: var(--_lumo-grid-border-color);
304
304
  }
305
305
 
306
+ [first-frozen-to-end] {
307
+ border-left: var(--_lumo-grid-border-width) solid transparent;
308
+ }
309
+
310
+ :host([overflow~='end']) [part~='cell'][first-frozen-to-end]:not([part~='details-cell']) {
311
+ border-left-color: var(--_lumo-grid-border-color);
312
+ }
313
+
306
314
  /* Row stripes */
307
315
 
308
316
  :host([theme~='row-stripes']) [part~='row']:not([odd]) [part~='body-cell'],
@@ -372,9 +380,18 @@ registerStyles(
372
380
  border-left: var(--_lumo-grid-border-width) solid transparent;
373
381
  }
374
382
 
375
- :host([dir='rtl'][overflow~='right']) [part~='cell'][last-frozen]:not([part~='details-cell']) {
383
+ :host([dir='rtl']) [first-frozen-to-end] {
384
+ border-left: none;
385
+ border-right: var(--_lumo-grid-border-width) solid transparent;
386
+ }
387
+
388
+ :host([dir='rtl'][overflow~='start']) [part~='cell'][last-frozen]:not([part~='details-cell']) {
376
389
  border-left-color: var(--_lumo-grid-border-color);
377
390
  }
391
+
392
+ :host([dir='rtl'][overflow~='end']) [part~='cell'][first-frozen-to-end]:not([part~='details-cell']) {
393
+ border-right-color: var(--_lumo-grid-border-color);
394
+ }
378
395
  `,
379
396
  { moduleId: 'lumo-grid' }
380
397
  );
@@ -114,6 +114,10 @@ registerStyles(
114
114
  border-right: 1px solid var(--material-divider-color);
115
115
  }
116
116
 
117
+ [part~='cell'][first-frozen-to-end] {
118
+ border-left: 1px solid var(--material-divider-color);
119
+ }
120
+
117
121
  /* Column resizing */
118
122
 
119
123
  [part~='cell']:not([last-frozen]) [part='resize-handle'] {
@@ -248,6 +252,11 @@ registerStyles(
248
252
  border-left: 1px solid var(--material-divider-color);
249
253
  }
250
254
 
255
+ :host([dir='rtl']) [part~='cell'][first-frozen-to-end] {
256
+ border-left: none;
257
+ border-right: 1px solid var(--material-divider-color);
258
+ }
259
+
251
260
  :host([dir='rtl']) [part~='cell']:not([last-frozen]) [part='resize-handle'] {
252
261
  border-right: none;
253
262
  border-left: 1px solid var(--material-divider-color);