import { afterNextRender, booleanAttribute, ChangeDetectionStrategy, Component, computed, ElementRef, forwardRef, inject, input, signal, ViewEncapsulation, } from "@angular/core"; import { SdKanbanBoard, type SdKanbanDragRef, type SdKanbanDropTarget, } from "./sd-kanban-board"; import { SdKanbanLane } from "./sd-kanban-lane"; import { SdResizeDirective, type SdResizeEvent } from "../../core/events/sd-resize"; @Component({ selector: "sd-kanban", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [SdResizeDirective], host: { "[attr.data-sd-dragging-this]": "dragKanban() === thisRef", "[attr.data-sd-dragging]": "dragKanban() != null", "[attr.data-sd-drag-over]": "dragOvered()", "(document:drop.capture)": "onDocumentDrop()", "(click)": "onHostClick($event)", }, template: `
`, styles: [ /* language=SCSS */ ` sd-kanban { position: relative; display: block; &[data-sd-dragging-this="true"] { display: none; } > ._drag-position { position: absolute; top: 0; left: 0; right: 0; pointer-events: none; } &[data-sd-dragging="true"] { > ._drag-position { pointer-events: auto; } > .card { pointer-events: none; cursor: pointer; } } > ._drop-position { border-radius: var(--border-radius-default); background: var(--trans-light); height: 0; margin-bottom: 0; visibility: hidden; transition: 0.1s linear; transition-property: height, margin-bottom, visibility; } &[data-sd-drag-over="true"] { > ._drop-position { margin-bottom: var(--gap-lg); visibility: visible; } } > .card { white-space: normal; user-select: none; margin-bottom: var(--gap-lg); } } `, ], }) export class SdKanban implements SdKanbanDragRef, SdKanbanDropTarget { private readonly _boardControl = inject>( forwardRef(() => SdKanbanBoard), ); private readonly _laneControl = inject>( forwardRef(() => SdKanbanLane), ); private readonly _elRef = inject>(ElementRef); readonly thisRef = this; value = input(); laneValue = computed(() => this._laneControl.value()); selectable = input(false, { transform: booleanAttribute }); draggable = input(false, { transform: booleanAttribute }); selected = computed( () => this.value() != null && this._boardControl.selectedValues().includes(this.value()!), ); dragKanban = computed(() => this._boardControl.dragKanban()); contentClass = input(); dragOvered = signal(false); heightOnDrag = signal(0); cardHeight = signal(0); constructor() { afterNextRender(() => { const card = this._elRef.nativeElement.querySelector(".card"); if (card != null) { const marginBottom = getComputedStyle(card).marginBottom; this.cardHeight.set(card.clientHeight + (parseInt(marginBottom) || 0)); } }); } targetLaneValue() { return this._laneControl.value(); } targetKanbanValue() { return this.value(); } onHostClick(event: MouseEvent) { if (event.shiftKey) { if (!this.selectable()) return; if (this.value() == null) return; event.preventDefault(); event.stopPropagation(); this._boardControl.selectedValues.update((v) => { const r = [...v]; if (v.includes(this.value()!)) { return r.filter((item) => item !== this.value()); } else { r.push(this.value()!); return r; } }); } } onDocumentDrop() { this.dragOvered.set(false); } onCardResize(event: SdResizeEvent) { const marginBottom = getComputedStyle(event.target).marginBottom; this.cardHeight.set( event.target.clientHeight + (parseInt(marginBottom) || 0), ); } onCardDragStart() { if (!this.draggable()) return; this.heightOnDrag.set(this._elRef.nativeElement.offsetHeight); this._boardControl.dragKanban.set(this); } onDragOver(event: DragEvent) { if (this._boardControl.dragKanban() == null) return; event.preventDefault(); event.stopPropagation(); this.dragOvered.set(true); } onDragLeave(event: DragEvent) { event.preventDefault(); event.stopPropagation(); this.dragOvered.set(false); } onDragDrop(event: DragEvent) { if (this._boardControl.dragKanban() == null) return; this.dragOvered.set(false); event.preventDefault(); event.stopPropagation(); this._boardControl.onDropTo(this); } }