Skip to content

Commit 602845e

Browse files
committed
Continue work on hving resize handle on whole column
1 parent 83f52ed commit 602845e

File tree

7 files changed

+176
-85
lines changed

7 files changed

+176
-85
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div style="height: 400px; max-width: 800px; overflow-y: scroll;">
2-
<FluentDataGrid @ref="grid" Items=@items Virtualize="true" DisplayMode="DataGridDisplayMode.Table" Style="width: 100%;" ItemSize="54" GenerateHeader="@GenerateHeaderOption.Sticky">
2+
<FluentDataGrid @ref="grid" Items=@items Virtualize="true" ResizableColumns="true" DisplayMode="DataGridDisplayMode.Table" Style="width: 100%;" ItemSize="54" GenerateHeader="@GenerateHeaderOption.Sticky">
33
<ChildContent>
44
<PropertyColumn Width="25%" Property="@(c => c.Item1)" Sortable="true" />
55
<PropertyColumn Width="25%" Property="@(c => c.Item2)" />
@@ -26,6 +26,25 @@
2626
</FluentSwitch>
2727
<FluentButton OnClick="SimulateDataLoading">Simulate data loading</FluentButton>
2828

29+
<div style="height: 400px; max-width: 800px; overflow-y: scroll;">
30+
<FluentDataGrid Items=@items Virtualize="true" ResizableColumns="true" DisplayMode="DataGridDisplayMode.Grid" Style="width: 100%;" ItemSize="54" GenerateHeader="@GenerateHeaderOption.Sticky">
31+
<ChildContent>
32+
<PropertyColumn Width="25%" Property="@(c => c.Item1)" Sortable="true" />
33+
<PropertyColumn Width="25%" Property="@(c => c.Item2)" />
34+
<PropertyColumn Width="25%" Property="@(c => c.Item3)" Align="Align.Center" />
35+
<PropertyColumn Width="25%" Property="@(c => c.Item4)" Align="Align.End" />
36+
</ChildContent>
37+
<EmptyContent>
38+
<FluentIcon Value="@(new Icons.Filled.Size24.Crown())" Color="@Color.Accent" />&nbsp; Nothing to see here. Carry on!
39+
</EmptyContent>
40+
<LoadingContent>
41+
<FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center">
42+
Loading...<br />
43+
<FluentProgress Width="240px" />
44+
</FluentStack>
45+
</LoadingContent>
46+
</FluentDataGrid>
47+
</div>
2948

3049
@code {
3150
FluentDataGrid<SampleGridData>? grid;
@@ -70,7 +89,7 @@
7089
items = null;
7190
grid?.SetLoadingState(true);
7291

73-
await Task.Delay(1500);
92+
//await Task.Delay(1500);
7493
7594
items = GenerateSampleGridData(5000);
7695
grid?.SetLoadingState(false);

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
::deep .resize-handle {
6666
position: absolute;
6767
top: 6px;
68-
right: -1px;
68+
right: 0;
6969
left: unset;
7070
bottom: 0;
7171
height: 30px;

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

Lines changed: 125 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -160,108 +160,153 @@ 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+
if (tableHeight < 70) {
174+
//get the aria rowcount attribute
175+
const rowCount = gridElement.getAttribute('aria-rowcount');
176+
if (rowCount) {
177+
const rowHeight = gridElement.querySelector('thead tr th').offsetHeight;
178+
// and multiply by the itemsize (== height of the header cells)
179+
tableHeight = rowCount * rowHeight;
180+
}
181+
}
182+
183+
headers.forEach((header) => {
174184
columns.push({
175185
header,
176-
size: `minmax(${minWidth}px,auto)`,
186+
size: `${header.clientWidth}px`,
177187
});
178188

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

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

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-
}
201+
if (isGrid) {
202+
gridElement.style.gridTemplateColumns = initialWidths;
203+
}
204+
}
194205

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

263-
export function resetColumnWidths(gridElement) {
264306

307+
308+
export function resetColumnWidths(gridElement) {
309+
const isGrid = gridElement.classList.contains('grid');
265310
const grid = grids.find(({ id }) => id === gridElement.id);
266311
if (!grid) {
267312
return;
@@ -270,11 +315,19 @@ export function resetColumnWidths(gridElement) {
270315
const columnsWidths = grid.initialWidths.split(' ');
271316

272317
grid.columns.forEach((column, index) => {
273-
column.size = columnsWidths[index];
318+
if (isGrid) {
319+
column.size = columnsWidths[index];
320+
} else {
321+
column.header.style.width = columnsWidths[index];
322+
}
274323
});
275324

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

@@ -308,7 +361,7 @@ export function resizeColumnDiscrete(gridElement, column, change) {
308361
}
309362
else {
310363
if (column.size.startsWith('minmax')) {
311-
column.size = parseInt(column.header.clientWidth, 10) + 'px';
364+
column.size = parseInt(column.header.clientWidth, 10) + 'px';
312365
}
313366
}
314367
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)

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ th, td {
33
}
44

55
td {
6-
padding: calc((var(--design-unit) + var(--focus-stroke-width) - var(--stroke-width)) * 1px) calc(((var(--design-unit) * 5) + var(--focus-stroke-width) - var(--stroke-width))* 1px);
6+
padding: 6px 16px;
77
overflow: hidden;
88
text-overflow: ellipsis;
99
white-space: nowrap;
@@ -20,10 +20,15 @@ td {
2020
text-align: end;
2121
}
2222

23-
td.grid-cell-placeholder:after {
24-
content: '\2026'; /*horizontal ellipsis*/
25-
opacity: 0.75;
26-
}
23+
th.col-justify-end > div {
24+
justify-content: flex-end;
25+
}
26+
27+
28+
td.grid-cell-placeholder:after {
29+
content: '\2026'; /*horizontal ellipsis*/
30+
opacity: 0.75;
31+
}
2732

2833

2934
.empty-content-cell,
@@ -86,11 +91,14 @@ td {
8691
margin-inline-start: 2px;
8792
}
8893

94+
.col-justify-start ::deep .col-title {
95+
text-align: left;
96+
}
97+
8998
.col-justify-center ::deep .col-title {
9099
text-align: center;
91100
}
92101

93102
.col-justify-end ::deep .col-title {
94103
text-align: end;
95-
margin-inline-end: calc(var(--design-unit) * 4px);
96104
}

0 commit comments

Comments
 (0)