Initial commit: dashboard-elements-ui library

Angular library providing dashboard components including grid layout,
drag-and-drop widgets, resize handles, toolbar, config panel, layout
presets, and persistence services.
This commit is contained in:
Giuliano Silvestro
2026-03-09 08:44:10 +10:00
commit 0b33a4561e
79 changed files with 21730 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import { Injectable, signal, computed } from '@angular/core';
import type { DbResizeDirection, DbDragPreview, DbResizePreview } from '../types/dashboard.types';
import { pixelToGridPosition, clamp } from '../utils/grid.utils';
@Injectable()
export class DashboardDragService {
// Move state
readonly isDragging = signal(false);
readonly draggedWidgetId = signal<string | null>(null);
readonly dragStartCol = signal(0);
readonly dragStartRow = signal(0);
readonly dragPreviewCol = signal(0);
readonly dragPreviewRow = signal(0);
readonly dragPreviewColSpan = signal(0);
readonly dragPreviewRowSpan = signal(0);
// Resize state
readonly isResizing = signal(false);
readonly resizeWidgetId = signal<string | null>(null);
readonly resizeDirection = signal<DbResizeDirection | null>(null);
readonly resizeStartColSpan = signal(0);
readonly resizeStartRowSpan = signal(0);
readonly resizePreviewColSpan = signal(0);
readonly resizePreviewRowSpan = signal(0);
// Computed
readonly isActive = computed(() => this.isDragging() || this.isResizing());
readonly dragPreview = computed<DbDragPreview | null>(() => {
if (!this.isDragging()) return null;
return {
col: this.dragPreviewCol(),
row: this.dragPreviewRow(),
colSpan: this.dragPreviewColSpan(),
rowSpan: this.dragPreviewRowSpan(),
};
});
readonly resizePreview = computed<DbResizePreview | null>(() => {
if (!this.isResizing()) return null;
return {
colSpan: this.resizePreviewColSpan(),
rowSpan: this.resizePreviewRowSpan(),
};
});
startDrag(widgetId: string, col: number, row: number, colSpan: number, rowSpan: number): void {
this.isDragging.set(true);
this.draggedWidgetId.set(widgetId);
this.dragStartCol.set(col);
this.dragStartRow.set(row);
this.dragPreviewCol.set(col);
this.dragPreviewRow.set(row);
this.dragPreviewColSpan.set(colSpan);
this.dragPreviewRowSpan.set(rowSpan);
}
updateDragPreview(col: number, row: number): void {
this.dragPreviewCol.set(col);
this.dragPreviewRow.set(row);
}
endDrag(): { widgetId: string; col: number; row: number } | null {
const widgetId = this.draggedWidgetId();
const col = this.dragPreviewCol();
const row = this.dragPreviewRow();
if (!widgetId) {
this.cancelAll();
return null;
}
this.isDragging.set(false);
this.draggedWidgetId.set(null);
this.dragStartCol.set(0);
this.dragStartRow.set(0);
this.dragPreviewCol.set(0);
this.dragPreviewRow.set(0);
this.dragPreviewColSpan.set(0);
this.dragPreviewRowSpan.set(0);
return { widgetId, col, row };
}
startResize(
widgetId: string,
direction: DbResizeDirection,
colSpan: number,
rowSpan: number,
): void {
this.isResizing.set(true);
this.resizeWidgetId.set(widgetId);
this.resizeDirection.set(direction);
this.resizeStartColSpan.set(colSpan);
this.resizeStartRowSpan.set(rowSpan);
this.resizePreviewColSpan.set(colSpan);
this.resizePreviewRowSpan.set(rowSpan);
}
updateResizePreview(colSpan: number, rowSpan: number): void {
this.resizePreviewColSpan.set(colSpan);
this.resizePreviewRowSpan.set(rowSpan);
}
endResize(): { widgetId: string; colSpan: number; rowSpan: number } | null {
const widgetId = this.resizeWidgetId();
const colSpan = this.resizePreviewColSpan();
const rowSpan = this.resizePreviewRowSpan();
if (!widgetId) {
this.cancelAll();
return null;
}
this.isResizing.set(false);
this.resizeWidgetId.set(null);
this.resizeDirection.set(null);
this.resizeStartColSpan.set(0);
this.resizeStartRowSpan.set(0);
this.resizePreviewColSpan.set(0);
this.resizePreviewRowSpan.set(0);
return { widgetId, colSpan, rowSpan };
}
cancelAll(): void {
this.isDragging.set(false);
this.draggedWidgetId.set(null);
this.dragStartCol.set(0);
this.dragStartRow.set(0);
this.dragPreviewCol.set(0);
this.dragPreviewRow.set(0);
this.dragPreviewColSpan.set(0);
this.dragPreviewRowSpan.set(0);
this.isResizing.set(false);
this.resizeWidgetId.set(null);
this.resizeDirection.set(null);
this.resizeStartColSpan.set(0);
this.resizeStartRowSpan.set(0);
this.resizePreviewColSpan.set(0);
this.resizePreviewRowSpan.set(0);
}
calculateGridPosition(
event: DragEvent | MouseEvent,
gridEl: HTMLElement,
columns: number,
rowHeight: number,
gap: number,
): { col: number; row: number } {
const gridRect = gridEl.getBoundingClientRect();
return pixelToGridPosition(event.clientX, event.clientY, gridRect, columns, rowHeight, gap);
}
}