Skip to content

Commit 46c2853

Browse files
authored
[DataGrid] Resize enhancements (#3767)
* Prep for next release * - Adjust header button width - Change resize handle to use a plain div instead of FluentDivider - Change styling to adapt to the above * Continue work on hving resize handle on whole column * - Update comments - Update Unit Test verified files
1 parent f45094a commit 46c2853

17 files changed

+189
-122
lines changed

examples/Demo/Shared/Pages/DataGrid/Examples/DataGridVirtualize.razor

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
</FluentSwitch>
2727
<FluentButton OnClick="SimulateDataLoading">Simulate data loading</FluentButton>
2828

29-
3029
@code {
3130
FluentDataGrid<SampleGridData>? grid;
3231
FluentSwitch? _clearToggle;

src/Core/Components/DataGrid/Columns/ColumnBase.razor

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
@if (AnyColumnActionEnabled)
2323
{
24-
<FluentButton Disabled="@(!AnyColumnActionEnabled)" Id="@_columnId" Appearance="Appearance.Stealth" Class="col-sort-button" Style="width: calc(100% - 20px);" @onclick="@HandleColumnHeaderClickedAsync" aria-label="@tooltip" title="@tooltip">
24+
<FluentButton Disabled="@(!AnyColumnActionEnabled)" Id="@_columnId" Appearance="Appearance.Stealth" Class="col-sort-button" Style="width: calc(100% - 10px);" @onclick="@HandleColumnHeaderClickedAsync" aria-label="@tooltip" title="@tooltip">
2525
<div class="col-title-text" title="@tooltip">@Title</div>
2626

2727
@if (Grid.SortByAscending.HasValue && IsActiveSortColumn)
@@ -90,13 +90,24 @@
9090
else
9191
{
9292
string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null;
93-
string? wdelta = "20px";
93+
string? wdelta = "10px";
94+
string? align;
9495

9596
if (Grid.ResizeType is not null || ColumnOptions is not null)
9697
{
9798
wdelta = "56px";
9899
}
99-
<div style="display: flex;">
100+
101+
// determine align string based on Align value
102+
align = Align switch
103+
{
104+
Align.Start => "flex-start",
105+
Align.Center => "center",
106+
Align.End => "flex-end",
107+
_ => "flex-start"
108+
};
109+
110+
<div style="display: flex; justify-content: @align;">
100111
@if (Align == Align.Start || Align == Align.Center)
101112
{
102113
@if (Grid.ResizeType is not null)

src/Core/Components/DataGrid/Columns/ColumnBase.razor.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.col-title {
2-
padding: 0.4rem 0.8rem;
2+
padding: 6px 16px;
33
user-select: none;
44
}
55

src/Core/Components/DataGrid/FluentDataGrid.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@
193193

194194
@if (ResizableColumns)
195195
{
196-
<FluentDivider Class="resize-handle" Orientation="Orientation.Vertical" Role="DividerRole.Separator" />
196+
<div class="resize-handle"></div>
197197
}
198198
</FluentDataGridCell>
199199
}

src/Core/Components/DataGrid/FluentDataGrid.razor.css

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,15 @@
6464

6565
::deep .resize-handle {
6666
position: absolute;
67-
top: 5px;
67+
top: 6px;
6868
right: 0;
6969
left: unset;
7070
bottom: 0;
71-
height: 32px;
71+
height: 30px;
7272
cursor: col-resize;
73-
margin-left: calc(var(--design-unit) * 2px);
74-
width: calc(var(--design-unit) * 1px + 2px);
75-
}
76-
77-
[dir=rtl] * ::deep .resize-handle {
78-
left: 0;
79-
right: unset;
73+
width: 6px;
74+
border-inline-end: 1px solid var(--neutral-stroke-divider-rest);
75+
;
8076
}
8177

8278
.header {

src/Core/Components/DataGrid/FluentDataGrid.razor.js

Lines changed: 126 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -160,108 +160,154 @@ export function checkColumnPopupPosition(gridElement, selector) {
160160

161161
export function enableColumnResizing(gridElement) {
162162
const columns = [];
163-
let min = 75;
164-
let headerBeingResized;
165-
let resizeHandle;
166-
167163
const headers = gridElement.querySelectorAll('.column-header.resizable');
168164

169165
if (headers.length === 0) {
170166
return;
171167
}
172168

173-
headers.forEach(header => {
169+
const isRTL = getComputedStyle(gridElement).direction === 'rtl';
170+
const isGrid = gridElement.classList.contains('grid')
171+
172+
let tableHeight = gridElement.offsetHeight;
173+
// rows have not been loaded yet, so we need to calculate the height
174+
if (tableHeight < 70) {
175+
// by getting the aria rowcount attribute
176+
const rowCount = gridElement.getAttribute('aria-rowcount');
177+
if (rowCount) {
178+
const rowHeight = gridElement.querySelector('thead tr th').offsetHeight;
179+
// and multiply by the itemsize (== height of the header cells)
180+
tableHeight = rowCount * rowHeight;
181+
}
182+
}
183+
184+
headers.forEach((header) => {
174185
columns.push({
175186
header,
176-
size: `minmax(${minWidth}px,auto)`,
187+
size: `${header.clientWidth}px`,
177188
});
178189

179-
const onPointerMove = (e) => requestAnimationFrame(() => {
180-
if (!headerBeingResized) {
181-
return;
182-
}
183-
gridElement.style.tableLayout = "fixed";
190+
const div = createDiv(tableHeight, isRTL);
191+
header.appendChild(div);
192+
header.style.position = 'relative';
193+
setListeners(div, isRTL);
194+
});
184195

185-
const horizontalScrollOffset = document.documentElement.scrollLeft;
186-
let width;
196+
let initialWidths;
197+
if (gridElement.style.gridTemplateColumns) {
198+
initialWidths = gridElement.style.gridTemplateColumns;
199+
} else {
200+
initialWidths = columns.map(({ size }) => size).join(' ');
187201

188-
if (document.body.dir === '' || document.body.dir === 'ltr') {
189-
width = (horizontalScrollOffset + e.clientX) - headerBeingResized.getClientRects()[0].x;
190-
}
191-
else {
192-
width = headerBeingResized.getClientRects()[0].x + headerBeingResized.clientWidth - (horizontalScrollOffset + e.clientX);
193-
}
202+
if (isGrid) {
203+
gridElement.style.gridTemplateColumns = initialWidths;
204+
}
205+
}
194206

195-
const column = columns.find(({ header }) => header === headerBeingResized);
196-
column.size = Math.max(minWidth, width) + 'px';
207+
const id = gridElement.id;
208+
grids.push({
209+
id,
210+
columns,
211+
initialWidths,
212+
});
197213

198-
columns.forEach((column) => {
199-
if (column.size.startsWith('minmax')) {
200-
column.size = parseInt(column.header.clientWidth, 10) + 'px';
201-
}
202-
});
214+
function setListeners(div, isRTL) {
215+
let pageX, curCol, curColWidth;
216+
217+
div.addEventListener('pointerdown', function (e) {
218+
curCol = e.target.parentElement;
219+
pageX = e.pageX;
220+
221+
const padding = paddingDiff(curCol);
203222

204-
gridElement.style.gridTemplateColumns = columns
205-
.map(({ size }) => size)
206-
.join(' ');
223+
curColWidth = curCol.offsetWidth - padding;
207224
});
208225

209-
const onPointerUp = (e) => {
226+
div.addEventListener('pointerover', function (e) {
227+
e.target.style.borderInlineEnd = '2px solid var(--neutral-stroke-focus)';
228+
});
210229

211-
window.removeEventListener('pointermove', onPointerMove);
212-
window.removeEventListener('pointerup', onPointerUp);
213-
window.removeEventListener('pointercancel', onPointerUp);
214-
window.removeEventListener('pointerleave', onPointerUp);
230+
div.addEventListener('pointerup', removeBorder);
231+
div.addEventListener('pointercancel', removeBorder);
232+
div.addEventListener('pointerleave', removeBorder);
215233

216-
headerBeingResized.classList.remove('header-being-resized');
217-
headerBeingResized = null;
234+
document.addEventListener('pointermove', (e) =>
235+
requestAnimationFrame(() => {
236+
gridElement.style.tableLayout = 'fixed';
218237

219-
if (e.target.hasPointerCapture(e.pointerId)) {
220-
e.target.releasePointerCapture(e.pointerId);
221-
}
222-
};
238+
if (curCol) {
239+
const diffX = isRTL ? pageX - e.pageX : e.pageX - pageX;
240+
const column = columns.find(({ header }) => header === curCol);
223241

224-
const initResize = ({ target, pointerId }) => {
225-
headerBeingResized = target.parentNode;
226-
headerBeingResized.classList.add('header-being-resized');
242+
column.size = parseInt(Math.max(minWidth, curColWidth + diffX), 10) + 'px';
227243

244+
columns.forEach((col) => {
245+
if (col.size.startsWith('minmax')) {
246+
col.size = parseInt(col.header.clientWidth, 10) + 'px';
247+
}
248+
});
228249

229-
window.addEventListener('pointermove', onPointerMove);
230-
window.addEventListener('pointerup', onPointerUp);
231-
window.addEventListener('pointercancel', onPointerUp);
232-
window.addEventListener('pointerleave', onPointerUp);
250+
if (isGrid) {
251+
gridElement.style.gridTemplateColumns = columns
252+
.map(({ size }) => size)
253+
.join(' ');
254+
}
255+
else {
256+
curCol.style.width = column.size;
257+
}
258+
}
259+
})
260+
);
233261

234-
if (resizeHandle) {
235-
resizeHandle.setPointerCapture(pointerId);
236-
}
237-
};
262+
document.addEventListener('pointerup', function () {
263+
curCol = undefined;
264+
curColWidth = undefined;
265+
pageX = undefined;
266+
});
267+
}
238268

239-
header.querySelector('.resize-handle').addEventListener('pointerdown', initResize);
269+
function createDiv(height, isRTL) {
270+
const div = document.createElement('div');
271+
div.style.top = '5px';
272+
div.style.position = 'absolute';
273+
div.style.cursor = 'col-resize';
274+
div.style.userSelect = 'none';
275+
div.style.height = height + 'px';
276+
div.style.width = '5px';
277+
278+
if (isRTL) {
279+
div.style.left = '0px';
280+
div.style.right = 'unset';
281+
} else {
282+
div.style.left = 'unset';
283+
div.style.right = '0px';
284+
}
285+
return div;
286+
}
240287

241-
});
288+
function paddingDiff(col) {
289+
if (getStyleVal(col, 'box-sizing') === 'border-box') {
290+
return 0;
291+
}
242292

243-
let initialWidths;
244-
if (gridElement.style.gridTemplateColumns) {
245-
initialWidths = gridElement.style.gridTemplateColumns;
293+
const padLeft = getStyleVal(col, 'padding-left');
294+
const padRight = getStyleVal(col, 'padding-right');
295+
return parseInt(padLeft) + parseInt(padRight);
246296
}
247-
else {
248-
initialWidths = columns
249-
.map(({ header, size }) => size)
250-
.join(' ');
251297

252-
gridElement.style.gridTemplateColumns = initialWidths;
298+
function getStyleVal(elm, css) {
299+
return window.getComputedStyle(elm, null).getPropertyValue(css);
253300
}
254301

255-
let id = gridElement.id;
256-
grids.push({
257-
id,
258-
columns,
259-
initialWidths
260-
});
302+
function removeBorder(e) {
303+
e.target.style.borderInlineEnd = '';
304+
}
261305
}
262306

263-
export function resetColumnWidths(gridElement) {
264307

308+
309+
export function resetColumnWidths(gridElement) {
310+
const isGrid = gridElement.classList.contains('grid');
265311
const grid = grids.find(({ id }) => id === gridElement.id);
266312
if (!grid) {
267313
return;
@@ -270,11 +316,19 @@ export function resetColumnWidths(gridElement) {
270316
const columnsWidths = grid.initialWidths.split(' ');
271317

272318
grid.columns.forEach((column, index) => {
273-
column.size = columnsWidths[index];
319+
if (isGrid) {
320+
column.size = columnsWidths[index];
321+
} else {
322+
column.header.style.width = columnsWidths[index];
323+
}
274324
});
275325

276-
gridElement.style.gridTemplateColumns = grid.initialWidths;
277-
gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true }));
326+
if (isGrid) {
327+
gridElement.style.gridTemplateColumns = grid.initialWidths;
328+
}
329+
gridElement.dispatchEvent(
330+
new CustomEvent('closecolumnresize', { bubbles: true })
331+
);
278332
gridElement.focus();
279333
}
280334

@@ -308,7 +362,7 @@ export function resizeColumnDiscrete(gridElement, column, change) {
308362
}
309363
else {
310364
if (column.size.startsWith('minmax')) {
311-
column.size = parseInt(column.header.clientWidth, 10) + 'px';
365+
column.size = parseInt(column.header.clientWidth, 10) + 'px';
312366
}
313367
}
314368
columns.push(column.size);

src/Core/Components/DataGrid/FluentDataGridCell.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public partial class FluentDataGridCell<TGridItem> : FluentComponentBase
7575
.AddStyle("padding-top", "calc(var(--design-unit) * 2.5px)", Column is SelectColumn<TGridItem> && (Grid.RowSize == DataGridRowSize.Medium || Owner.RowType == DataGridRowType.Header))
7676
.AddStyle("padding-top", "calc(var(--design-unit) * 1.5px)", Column is SelectColumn<TGridItem> && Grid.RowSize == DataGridRowSize.Small && Owner.RowType == DataGridRowType.Default)
7777
.AddStyle("width", Column?.Width, !string.IsNullOrEmpty(Column?.Width) && Grid.DisplayMode == DataGridDisplayMode.Table)
78-
.AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize && Owner.RowType == DataGridRowType.Default)
78+
.AddStyle("height", $"{Grid.ItemSize:0}px", () => !Grid.EffectiveLoadingValue && Grid.Virtualize)
7979
.AddStyle("height", $"{(int)Grid.RowSize}px", () => !Grid.EffectiveLoadingValue && !Grid.Virtualize && !Grid.MultiLine && (Grid.Items is not null || Grid.ItemsProvider is not null))
8080
.AddStyle("height", "100%", Grid.MultiLine)
8181
.AddStyle("min-height", "44px", Owner.RowType != DataGridRowType.Default)

0 commit comments

Comments
 (0)