Skip to content

Commit fe7d0e7

Browse files
committed
feat: added support for rowHeight = 'fit', also added Input() height
closes: #1, #51, #58
1 parent 033bcac commit fe7d0e7

File tree

13 files changed

+268
-22
lines changed

13 files changed

+268
-22
lines changed

projects/angular-grid-layout/src/lib/grid.component.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { coerceNumberProperty, NumberInput } from './coercion/number-property';
66
import { KtdGridItemComponent } from './grid-item/grid-item.component';
77
import { combineLatest, merge, NEVER, Observable, Observer, of, Subscription } from 'rxjs';
88
import { exhaustMap, map, startWith, switchMap, takeUntil } from 'rxjs/operators';
9-
import { ktdGridItemDragging, ktdGridItemLayoutItemAreEqual, ktdGridItemResizing } from './utils/grid.utils';
9+
import { ktdGetGridItemRowHeight, ktdGridItemDragging, ktdGridItemLayoutItemAreEqual, ktdGridItemResizing } from './utils/grid.utils';
1010
import { compact } from './utils/react-grid-layout.utils';
1111
import {
1212
GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridCfg, KtdGridCompactType, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem
@@ -49,16 +49,17 @@ function getDragResizeEventData(gridItem: KtdGridItemComponent, layout: KtdGridL
4949

5050
function layoutToRenderItems(config: KtdGridCfg, width: number, height: number): KtdDictionary<KtdGridItemRenderData<number>> {
5151
const {cols, rowHeight, layout, gap} = config;
52-
const widthExcludinggap = width - Math.max((gap * (cols - 1)), 0);
53-
const itemWidthPerColumn = (widthExcludinggap / cols);
52+
const rowHeightInPixels = rowHeight === 'fit' ? ktdGetGridItemRowHeight(layout, height, gap) : rowHeight;
53+
const widthExcludingGap = width - Math.max((gap * (cols - 1)), 0);
54+
const itemWidthPerColumn = (widthExcludingGap / cols);
5455
const renderItems: KtdDictionary<KtdGridItemRenderData<number>> = {};
5556
for (const item of layout) {
5657
renderItems[item.id] = {
5758
id: item.id,
58-
top: item.y * rowHeight + gap * item.y,
59+
top: item.y * rowHeightInPixels + gap * item.y,
5960
left: item.x * itemWidthPerColumn + gap * item.x,
6061
width: item.w * itemWidthPerColumn + gap * Math.max(item.w - 1, 0),
61-
height: item.h * rowHeight + gap * Math.max(item.h - 1, 0),
62+
height: item.h * rowHeightInPixels + gap * Math.max(item.h - 1, 0),
6263
};
6364
}
6465
return renderItems;
@@ -179,13 +180,13 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
179180

180181
/** Row height in css pixels */
181182
@Input()
182-
get rowHeight(): number { return this._rowHeight; }
183+
get rowHeight(): number | 'fit' { return this._rowHeight; }
183184

184-
set rowHeight(val: number) {
185-
this._rowHeight = Math.max(1, Math.round(coerceNumberProperty(val)));
185+
set rowHeight(val: number | 'fit') {
186+
this._rowHeight = val === 'fit' ? val : Math.max(1, Math.round(coerceNumberProperty(val)));
186187
}
187188

188-
private _rowHeight: number = 100;
189+
private _rowHeight: number | 'fit' = 100;
189190

190191
/** Number of columns */
191192
@Input()
@@ -228,10 +229,24 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
228229

229230
private _gap: number = 0;
230231

232+
@Input()
233+
get height(): number | null {
234+
return this._height;
235+
}
236+
237+
set height(val: number | null) {
238+
this._height = typeof val === 'number' ? Math.max(val, 0) : null;
239+
}
240+
241+
/** Total height of the grid */
242+
private _height: number | null = null;
243+
private gridCurrentHeight: number;
244+
231245
get config(): KtdGridCfg {
232246
return {
233247
cols: this.cols,
234248
rowHeight: this.rowHeight,
249+
height: this.height,
235250
layout: this.layout,
236251
preventCollision: this.preventCollision,
237252
gap: this.gap,
@@ -244,8 +259,6 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
244259
/** Element that is rendered as placeholder when a grid item is being dragged */
245260
private placeholder: HTMLElement | null;
246261

247-
/** Total height of the grid */
248-
private _height: number;
249262
private _gridItemsRenderData: KtdDictionary<KtdGridItemRenderData<number>>;
250263
private subscriptions: Subscription[];
251264

@@ -258,6 +271,11 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
258271
}
259272

260273
ngOnChanges(changes: SimpleChanges) {
274+
275+
if (this.rowHeight === 'fit' && this.height == null) {
276+
console.warn(`KtdGridComponent: The @Input() height should not be null when using rowHeight 'fit'`);
277+
}
278+
261279
let needsCompactLayout = false;
262280
let needsRecalculateRenderData = false;
263281

@@ -268,7 +286,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
268286
}
269287

270288
// Check if wee need to recalculate rendering data.
271-
if (needsCompactLayout || changes.rowHeight || changes.gap) {
289+
if (needsCompactLayout || changes.rowHeight || changes.height || changes.gap) {
272290
needsRecalculateRenderData = true;
273291
}
274292

@@ -315,12 +333,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
315333

316334
calculateRenderData() {
317335
const clientRect = (this.elementRef.nativeElement as HTMLElement).getBoundingClientRect();
318-
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, clientRect.height);
319-
this._height = getGridHeight(this.layout, this.rowHeight, this.gap);
336+
this.gridCurrentHeight = this.height ?? (this.rowHeight === 'fit' ? clientRect.height : getGridHeight(this.layout, this.rowHeight, this.gap));
337+
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, this.gridCurrentHeight);
320338
}
321339

322340
render() {
323-
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this._height}px`);
341+
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this.gridCurrentHeight}px`);
324342
this.updateGridItemsStyles();
325343
}
326344

@@ -439,6 +457,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
439457
const {layout, draggedItemPos} = calcNewStateFunc(gridItem, {
440458
layout: currentLayout,
441459
rowHeight: this.rowHeight,
460+
height: this.height,
442461
cols: this.cols,
443462
preventCollision: this.preventCollision,
444463
gap: this.gap,
@@ -451,11 +470,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
451470
});
452471
newLayout = layout;
453472

454-
this._height = getGridHeight(newLayout, this.rowHeight, this.gap);
473+
this.gridCurrentHeight = this.height ?? (this.rowHeight === 'fit' ? gridElemClientRect.height : getGridHeight(newLayout, this.rowHeight, this.gap))
455474

456475
this._gridItemsRenderData = layoutToRenderItems({
457476
cols: this.cols,
458477
rowHeight: this.rowHeight,
478+
height: this.height,
459479
layout: newLayout,
460480
preventCollision: this.preventCollision,
461481
gap: this.gap,

projects/angular-grid-layout/src/lib/grid.definitions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export type KtdGridCompactType = CompactType;
1818

1919
export interface KtdGridCfg {
2020
cols: number;
21-
rowHeight: number; // row height in pixels
21+
rowHeight: number | 'fit'; // row height in pixels
22+
height: number | null;
2223
layout: KtdGridLayoutItem[];
2324
preventCollision: boolean;
2425
gap: number;

projects/angular-grid-layout/src/lib/utils/grid.utils.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ export function ktdTrackById(index: number, item: {id: string}) {
1111
return item.id;
1212
}
1313

14+
/** Given a layout, the gridHeight and the gap return the resulting rowHeight */
15+
export function ktdGetGridItemRowHeight(layout: KtdGridLayout, gridHeight: number, gap: number): number {
16+
const numberOfRows = layout.reduce((acc, cur) => Math.max(acc, Math.max(cur.y + cur.h, 0)), 0);
17+
const gapTotalHeight = (numberOfRows - 1) * gap;
18+
const gridHeightMinusGap = gridHeight - gapTotalHeight;
19+
return gridHeightMinusGap / numberOfRows;
20+
}
21+
1422
/**
1523
* Call react-grid-layout utils 'compact()' function and return the compacted layout.
1624
* @param layout to be compacted.
@@ -95,11 +103,15 @@ export function ktdGridItemDragging(gridItem: KtdGridItemComponent, config: KtdG
95103
const gridRelXPos = clientX - gridElementLeftPosition - offsetX;
96104
const gridRelYPos = clientY - gridElementTopPosition - offsetY;
97105

106+
const rowHeightInPixels = config.rowHeight === 'fit'
107+
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
108+
: config.rowHeight;
109+
98110
// Get layout item position
99111
const layoutItem: KtdGridLayoutItem = {
100112
...draggingElemPrevItem,
101113
x: screenXToGridX(gridRelXPos , config.cols, gridElemClientRect.width, config.gap),
102-
y: screenYToGridY(gridRelYPos, config.rowHeight, gridElemClientRect.height, config.gap)
114+
y: screenYToGridY(gridRelYPos, rowHeightInPixels, gridElemClientRect.height, config.gap)
103115
};
104116

105117
// Correct the values if they overflow, since 'moveElement' function doesn't do it
@@ -161,11 +173,15 @@ export function ktdGridItemResizing(gridItem: KtdGridItemComponent, config: KtdG
161173
const width = clientX + resizeElemOffsetX - (dragElemClientRect.left + scrollDifference.left);
162174
const height = clientY + resizeElemOffsetY - (dragElemClientRect.top + scrollDifference.top);
163175

176+
const rowHeightInPixels = config.rowHeight === 'fit'
177+
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
178+
: config.rowHeight;
179+
164180
// Get layout item grid position
165181
const layoutItem: KtdGridLayoutItem = {
166182
...draggingElemPrevItem,
167183
w: screenWidthToGridWidth(width, config.cols, gridElemClientRect.width, config.gap),
168-
h: screenHeightToGridHeight(height, config.rowHeight, gridElemClientRect.height, config.gap)
184+
h: screenHeightToGridHeight(height, rowHeightInPixels, gridElemClientRect.height, config.gap)
169185
};
170186

171187
layoutItem.w = limitNumberWithinRange(layoutItem.w, gridItem.minW ?? layoutItem.minW, gridItem.maxW ?? layoutItem.maxW);

projects/demo-app/src/app/app-routing.module.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const routes: Routes = [
2626
redirectTo: 'scroll-test',
2727
pathMatch: 'full'
2828
},
29+
{
30+
path: 'row-height-fit',
31+
loadComponent: () => import('./row-height-fit/row-height-fit.component').then(m => m.KtdRowHeightFitComponent),
32+
data: {title: 'Angular Grid Layout - Row Height Fit'}
33+
},
2934
{
3035
path: '**',
3136
redirectTo: 'playground'

projects/demo-app/src/app/app.component.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
:host {
22
width: 100%;
3+
height: 100%;
34
display: block;
45
background-color: var(--ktd-background-color-step-50);
56
box-sizing: border-box;

projects/demo-app/src/app/custom-handles/custom-handles.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ <h2 style="margin-top: 16px">Other examples: </h2>
2525
<a [routerLink]="['/playground']">Playground</a>
2626
<a [routerLink]="['/real-life-example']">Real life example</a>
2727
<a [routerLink]="['/scroll-test']">Scroll test</a>
28+
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
2829
</div>

projects/demo-app/src/app/playground/playground.component.html

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616
<mat-label>Columns</mat-label>
1717
<input matInput type="number" [value]="cols + ''" (input)="onColsChange($event)">
1818
</mat-form-field>
19-
<mat-form-field color="accent" style="width: 64px">
19+
<mat-form-field color="accent" style="width: 120px">
2020
<mat-label>Row height</mat-label>
21-
<input matInput type="number" [value]="rowHeight + ''" (input)="onRowHeightChange($event)">
21+
<input matInput type="number" [value]="rowHeight + ''" style="width: 60%;" [disabled]="rowHeightFit" (input)="onRowHeightChange($event)">
22+
<mat-checkbox style="position: absolute; right: 4px; bottom: 4px;" (change)="onRowHeightFitChange($event)">Fit</mat-checkbox>
23+
</mat-form-field>
24+
<mat-form-field color="accent" style="width: 94px">
25+
<mat-label>Grid height</mat-label>
26+
<input matInput type="number" [value]="gridHeight === null ? null : gridHeight + ''" [disabled]="!rowHeightFit" (input)="onGridHeightChange($event)">
2227
</mat-form-field>
2328
<mat-form-field color="accent" style="width: 128px">
2429
<mat-label>Drag Threshold</mat-label>
@@ -80,7 +85,8 @@
8085

8186
<div class="grid-container">
8287
<ktd-grid [cols]="cols"
83-
[rowHeight]="rowHeight"
88+
[height]="rowHeightFit && gridHeight ? gridHeight : null"
89+
[rowHeight]="rowHeightFit ? 'fit' : rowHeight"
8490
[layout]="layout"
8591
[compactType]="compactType"
8692
[preventCollision]="preventCollision"
@@ -118,4 +124,5 @@ <h2 style="margin-top: 16px">Other examples: </h2>
118124
<a [routerLink]="['/custom-handles']">Custom handles</a>
119125
<a [routerLink]="['/real-life-example']">Real life example</a>
120126
<a [routerLink]="['/scroll-test']">Scroll test</a>
127+
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
121128
</div>

projects/demo-app/src/app/playground/playground.component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { ktdArrayRemoveItem } from '../utils';
99
import { DOCUMENT } from '@angular/common';
1010
import { coerceNumberProperty } from '@angular/cdk/coercion';
11+
import { MatCheckboxChange } from '@angular/material/checkbox';
1112

1213
@Component({
1314
selector: 'ktd-playground',
@@ -20,6 +21,8 @@ export class KtdPlaygroundComponent implements OnInit, OnDestroy {
2021

2122
cols = 12;
2223
rowHeight = 50;
24+
rowHeightFit = false;
25+
gridHeight: null | number = null;
2326
compactType: 'vertical' | 'horizontal' | null = 'vertical';
2427
layout: KtdGridLayout = [
2528
{id: '0', x: 5, y: 0, w: 2, h: 3},
@@ -154,6 +157,14 @@ export class KtdPlaygroundComponent implements OnInit, OnDestroy {
154157
this.rowHeight = coerceNumberProperty((event.target as HTMLInputElement).value);
155158
}
156159

160+
onRowHeightFitChange(change: MatCheckboxChange) {
161+
this.rowHeightFit = change.checked;
162+
}
163+
164+
onGridHeightChange(event: Event) {
165+
this.gridHeight = coerceNumberProperty((event.target as HTMLInputElement).value);
166+
}
167+
157168
onDragStartThresholdChange(event: Event) {
158169
this.dragStartThreshold = coerceNumberProperty((event.target as HTMLInputElement).value);
159170
}

projects/demo-app/src/app/real-life-example/real-life-example.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ <h2 style="margin-top: 16px">Other examples: </h2>
154154
<a [routerLink]="['/playground']">Playground</a>
155155
<a [routerLink]="['/custom-handles']">Custom handles</a>
156156
<a [routerLink]="['/scroll-test']">Scroll test</a>
157+
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
157158
</div>
158159

159160

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<div class="grid-container" #gridContainer>
2+
<ktd-grid [cols]="cols"
3+
[height]="gridHeight"
4+
[rowHeight]="'fit'"
5+
[layout]="layout"
6+
[compactType]="compactType"
7+
[preventCollision]="preventCollision"
8+
[gap]="gap">
9+
<ktd-grid-item *ngFor="let item of layout; trackBy:trackById"
10+
[id]="item.id"
11+
[draggable]="!disableDrag"
12+
[resizable]="!disableResize">
13+
<div class="grid-item-content">{{item.id}}</div>
14+
<div class="grid-item-remove-handle"
15+
*ngIf="!disableRemove"
16+
(mousedown)="stopEventPropagation($event)"
17+
(click)="removeItem(item.id)">
18+
</div>
19+
</ktd-grid-item>
20+
</ktd-grid>
21+
</div>
22+
23+
<div class="footer-container">
24+
<h2 style="margin-top: 16px">Other examples: </h2>
25+
<div style="display: flex; flex-direction: column">
26+
<a [routerLink]="['/playground']">Playground</a>
27+
<a [routerLink]="['/custom-handles']">Custom handles</a>
28+
<a [routerLink]="['/real-life-example']">Real life example</a>
29+
<a [routerLink]="['/scroll-test']">Scroll test</a>
30+
</div>
31+
</div>

0 commit comments

Comments
 (0)