Skip to content

Commit

Permalink
feat: added support for rowHeight = 'fit', also added @input() height
Browse files Browse the repository at this point in the history
  • Loading branch information
llorenspujol committed Jan 3, 2023
1 parent 27a1290 commit 24dae63
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 39 deletions.
65 changes: 35 additions & 30 deletions projects/angular-grid-layout/src/lib/grid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { coerceNumberProperty, NumberInput } from './coercion/number-property';
import { KtdGridItemComponent } from './grid-item/grid-item.component';
import { combineLatest, merge, NEVER, Observable, Observer, of, Subscription } from 'rxjs';
import { exhaustMap, map, startWith, switchMap, takeUntil } from 'rxjs/operators';
import { ktdGridItemDragging, ktdGridItemLayoutItemAreEqual, ktdGridItemResizing } from './utils/grid.utils';
import { ktdGetGridItemRowHeight, ktdGridItemDragging, ktdGridItemLayoutItemAreEqual, ktdGridItemResizing } from './utils/grid.utils';
import { compact } from './utils/react-grid-layout.utils';
import {
GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridCfg, KtdGridCompactType, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem
Expand Down Expand Up @@ -49,16 +49,17 @@ function getDragResizeEventData(gridItem: KtdGridItemComponent, layout: KtdGridL

function layoutToRenderItems(config: KtdGridCfg, width: number, height: number): KtdDictionary<KtdGridItemRenderData<number>> {
const {cols, rowHeight, layout, gap} = config;
const widthExcludinggap = width - Math.max((gap * (cols - 1)), 0);
const itemWidthPerColumn = (widthExcludinggap / cols);
const rowHeightInPixels = rowHeight === 'fit' ? ktdGetGridItemRowHeight(layout, height, gap) : rowHeight;
const widthExcludingGap = width - Math.max((gap * (cols - 1)), 0);
const itemWidthPerColumn = (widthExcludingGap / cols);
const renderItems: KtdDictionary<KtdGridItemRenderData<number>> = {};
for (const item of layout) {
renderItems[item.id] = {
id: item.id,
top: item.y * rowHeight + gap * item.y,
top: item.y * rowHeightInPixels + gap * item.y,
left: item.x * itemWidthPerColumn + gap * item.x,
width: item.w * itemWidthPerColumn + gap * Math.max(item.w - 1, 0),
height: item.h * rowHeight + gap * Math.max(item.h - 1, 0),
height: item.h * rowHeightInPixels + gap * Math.max(item.h - 1, 0),
};
}
return renderItems;
Expand Down Expand Up @@ -228,10 +229,24 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte

private _gap: number = 0;

@Input()
get height(): number | null {
return this._height;
}

set height(val: number | null) {
this._height = typeof val === 'number' ? Math.max(val, 0) : null;
}

/** Total height of the grid */
private _height: number | null = null;
private gridCurrentHeight: number;

get config(): KtdGridCfg {
return {
cols: this.cols,
rowHeight: this.rowHeight as any,
rowHeight: this.rowHeight,
height: this.height,
layout: this.layout,
preventCollision: this.preventCollision,
gap: this.gap,
Expand All @@ -244,8 +259,6 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
/** Element that is rendered as placeholder when a grid item is being dragged */
private placeholder: HTMLElement | null;

/** Total height of the grid */
private _height: number;
private _gridItemsRenderData: KtdDictionary<KtdGridItemRenderData<number>>;
private subscriptions: Subscription[];

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

ngOnChanges(changes: SimpleChanges) {

if (this.rowHeight === 'fit' && this.height == null) {
console.warn(`KtdGridComponent: The @Input() height should not be null when using rowHeight 'fit'`);
}

let needsCompactLayout = false;
let needsRecalculateRenderData = false;

Expand All @@ -268,7 +286,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
}

// Check if wee need to recalculate rendering data.
if (needsCompactLayout || changes.rowHeight || changes.gap) {
if (needsCompactLayout || changes.rowHeight || changes.height || changes.gap) {
needsRecalculateRenderData = true;
}

Expand Down Expand Up @@ -315,24 +333,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte

calculateRenderData() {
const clientRect = (this.elementRef.nativeElement as HTMLElement).getBoundingClientRect();
if (this.rowHeight === 'fit') {
const totalRows = getGridHeight(this.layout, 1, this.gap);
this._height = clientRect.height;
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, clientRect.height);
} else {
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, clientRect.height);
this._height = getGridHeight(this.layout, this.rowHeight, this.gap);
}
this.gridCurrentHeight = this.height ?? (this.rowHeight === 'fit' ? clientRect.height : getGridHeight(this.layout, this.rowHeight, this.gap));
this._gridItemsRenderData = layoutToRenderItems(this.config, clientRect.width, this.gridCurrentHeight);
}

render() {
if (this.rowHeight === 'fit') {
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `100%`);

} else {
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this._height}px`);

}
this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${this.gridCurrentHeight}px`);
this.updateGridItemsStyles();
}

Expand Down Expand Up @@ -404,9 +410,6 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
}
this.createPlaceholderElement(placeholderClientRect, gridItem.placeholder);

const rowHeight = this.rowHeight === 'fit' ? (gridElemClientRect.height / getGridHeight(this.layout, 1, this.gap)) : this.rowHeight;


let newLayout: KtdGridLayoutItem[];

// TODO (enhancement): consider move this 'side effect' observable inside the main drag loop.
Expand Down Expand Up @@ -453,7 +456,8 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte

const {layout, draggedItemPos} = calcNewStateFunc(gridItem, {
layout: currentLayout,
rowHeight,
rowHeight: this.rowHeight,
height: this.height,
cols: this.cols,
preventCollision: this.preventCollision,
gap: this.gap,
Expand All @@ -466,11 +470,12 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
});
newLayout = layout;

this._height = getGridHeight(newLayout, rowHeight, this.gap);
this.gridCurrentHeight = this.height ?? (this.rowHeight === 'fit' ? gridElemClientRect.height : getGridHeight(newLayout, this.rowHeight, this.gap))

this._gridItemsRenderData = layoutToRenderItems({
cols: this.cols,
rowHeight,
rowHeight: this.rowHeight,
height: this.height,
layout: newLayout,
preventCollision: this.preventCollision,
gap: this.gap,
Expand Down
3 changes: 2 additions & 1 deletion projects/angular-grid-layout/src/lib/grid.definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type KtdGridCompactType = CompactType;

export interface KtdGridCfg {
cols: number;
rowHeight: number; // row height in pixels
rowHeight: number | 'fit'; // row height in pixels
height: number | null;
layout: KtdGridLayoutItem[];
preventCollision: boolean;
gap: number;
Expand Down
20 changes: 18 additions & 2 deletions projects/angular-grid-layout/src/lib/utils/grid.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export function ktdTrackById(index: number, item: {id: string}) {
return item.id;
}

/** Given a layout, the gridHeight and the gap return the resulting rowHeight */
export function ktdGetGridItemRowHeight(layout: KtdGridLayout, gridHeight: number, gap: number): number {
const numberOfRows = layout.reduce((acc, cur) => Math.max(acc, Math.max(cur.y + cur.h, 0)), 0);
const gapTotalHeight = (numberOfRows - 1) * gap;
const gridHeightMinusGap = gridHeight - gapTotalHeight;
return gridHeightMinusGap / numberOfRows;
}

/**
* Call react-grid-layout utils 'compact()' function and return the compacted layout.
* @param layout to be compacted.
Expand Down Expand Up @@ -95,11 +103,15 @@ export function ktdGridItemDragging(gridItem: KtdGridItemComponent, config: KtdG
const gridRelXPos = clientX - gridElementLeftPosition - offsetX;
const gridRelYPos = clientY - gridElementTopPosition - offsetY;

const rowHeightInPixels = config.rowHeight === 'fit'
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
: config.rowHeight;

// Get layout item position
const layoutItem: KtdGridLayoutItem = {
...draggingElemPrevItem,
x: screenXToGridX(gridRelXPos , config.cols, gridElemClientRect.width, config.gap),
y: screenYToGridY(gridRelYPos, config.rowHeight, gridElemClientRect.height, config.gap)
y: screenYToGridY(gridRelYPos, rowHeightInPixels, gridElemClientRect.height, config.gap)
};

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

const rowHeightInPixels = config.rowHeight === 'fit'
? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)
: config.rowHeight;

// Get layout item grid position
const layoutItem: KtdGridLayoutItem = {
...draggingElemPrevItem,
w: screenWidthToGridWidth(width, config.cols, gridElemClientRect.width, config.gap),
h: screenHeightToGridHeight(height, config.rowHeight, gridElemClientRect.height, config.gap)
h: screenHeightToGridHeight(height, rowHeightInPixels, gridElemClientRect.height, config.gap)
};

layoutItem.w = limitNumberWithinRange(layoutItem.w, gridItem.minW ?? layoutItem.minW, gridItem.maxW ?? layoutItem.maxW);
Expand Down
5 changes: 5 additions & 0 deletions projects/demo-app/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const routes: Routes = [
redirectTo: 'scroll-test',
pathMatch: 'full'
},
{
path: 'row-height-fit',
loadComponent: () => import('./row-height-fit/row-height-fit.component').then(m => m.KtdRowHeightFitComponent),
data: {title: 'Angular Grid Layout - Row Height Fit'}
},
{
path: '**',
redirectTo: 'playground'
Expand Down
1 change: 1 addition & 0 deletions projects/demo-app/src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
:host {
width: 100%;
height: 100%;
display: block;
background-color: var(--ktd-background-color-step-50);
box-sizing: border-box;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ <h2 style="margin-top: 16px">Other examples: </h2>
<a [routerLink]="['/playground']">Playground</a>
<a [routerLink]="['/real-life-example']">Real life example</a>
<a [routerLink]="['/scroll-test']">Scroll test</a>
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
</div>
18 changes: 12 additions & 6 deletions projects/demo-app/src/app/playground/playground.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@
<mat-label>Columns</mat-label>
<input matInput type="number" [value]="cols + ''" (input)="onColsChange($event)">
</mat-form-field>
<mat-form-field color="accent" style="width: 64px">
<mat-form-field color="accent" style="width: 120px">
<mat-label>Row height</mat-label>
<input matInput type="number" [value]="rowHeight + ''" (input)="onRowHeightChange($event)">
<input matInput type="number" [value]="rowHeight + ''" style="width: 60%;" [disabled]="rowHeightFit" (input)="onRowHeightChange($event)">
<mat-checkbox style="position: absolute; right: 4px; bottom: 4px;" (change)="onRowHeightFitChange($event)">Fit</mat-checkbox>
</mat-form-field>
<mat-form-field color="accent" style="width: 94px">
<mat-label>Grid height</mat-label>
<input matInput type="number" [value]="gridHeight === null ? null : gridHeight + ''" [disabled]="!rowHeightFit" (input)="onGridHeightChange($event)">
</mat-form-field>
<mat-form-field color="accent" style="width: 128px">
<mat-label>Drag Threshold</mat-label>
Expand Down Expand Up @@ -78,10 +83,10 @@
</mat-chip-list>
</div>

<div class="grid-container" [style.height]="'400px'">
<ktd-grid [style.height]="'400px'"
[cols]="cols"
[rowHeight]="'fit'"
<div class="grid-container">
<ktd-grid [cols]="cols"
[height]="rowHeightFit && gridHeight ? gridHeight : null"
[rowHeight]="rowHeightFit ? 'fit' : rowHeight"
[layout]="layout"
[compactType]="compactType"
[preventCollision]="preventCollision"
Expand Down Expand Up @@ -119,4 +124,5 @@ <h2 style="margin-top: 16px">Other examples: </h2>
<a [routerLink]="['/custom-handles']">Custom handles</a>
<a [routerLink]="['/real-life-example']">Real life example</a>
<a [routerLink]="['/scroll-test']">Scroll test</a>
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
</div>
11 changes: 11 additions & 0 deletions projects/demo-app/src/app/playground/playground.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { ktdArrayRemoveItem } from '../utils';
import { DOCUMENT } from '@angular/common';
import { coerceNumberProperty } from '@angular/cdk/coercion';
import { MatCheckboxChange } from '@angular/material/checkbox';

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

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

onRowHeightFitChange(change: MatCheckboxChange) {
this.rowHeightFit = change.checked;
}

onGridHeightChange(event: Event) {
this.gridHeight = coerceNumberProperty((event.target as HTMLInputElement).value);
}

onDragStartThresholdChange(event: Event) {
this.dragStartThreshold = coerceNumberProperty((event.target as HTMLInputElement).value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ <h2 style="margin-top: 16px">Other examples: </h2>
<a [routerLink]="['/playground']">Playground</a>
<a [routerLink]="['/custom-handles']">Custom handles</a>
<a [routerLink]="['/scroll-test']">Scroll test</a>
<a [routerLink]="['/row-height-fit']">Row Height Fit</a>
</div>


Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div class="grid-container" #gridContainer>
<ktd-grid [cols]="cols"
[height]="gridHeight"
[rowHeight]="'fit'"
[layout]="layout"
[compactType]="compactType"
[preventCollision]="preventCollision"
[gap]="gap">
<ktd-grid-item *ngFor="let item of layout; trackBy:trackById"
[id]="item.id"
[draggable]="!disableDrag"
[resizable]="!disableResize">
<div class="grid-item-content">{{item.id}}</div>
<div class="grid-item-remove-handle"
*ngIf="!disableRemove"
(mousedown)="stopEventPropagation($event)"
(click)="removeItem(item.id)">
</div>
</ktd-grid-item>
</ktd-grid>
</div>

<div class="footer-container">
<h2 style="margin-top: 16px">Other examples: </h2>
<div style="display: flex; flex-direction: column">
<a [routerLink]="['/playground']">Playground</a>
<a [routerLink]="['/custom-handles']">Custom handles</a>
<a [routerLink]="['/real-life-example']">Real life example</a>
<a [routerLink]="['/scroll-test']">Scroll test</a>
</div>
</div>
Loading

0 comments on commit 24dae63

Please sign in to comment.