/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { Directive, EventEmitter, HostListener, inject, Input, OnDestroy, Output, } from '@angular/core'; import { Subject } from 'rxjs'; import { filter, takeUntil, tap } from 'rxjs/operators'; import { DragService, UxDragEvent } from './drag.service'; @Directive({ selector: '[uxDrop]', host: { '[class.ux-drop-hover]': 'isMouseOver && isDragging && !dropDisabled', }, }) export class DropDirective implements OnDestroy { private readonly _dragService = inject>(DragService); /** Define a specific group of dragged items to listen to */ @Input() group: string | string[]; /** Define whether or not dropping is enabled */ @Input() dropDisabled: boolean = false; /** Emit the model of the item dropped */ @Output() onDrop = new EventEmitter(); /** Determine whether or not the mouse is within the drop region */ isMouseOver: boolean = false; /** Determine whether or not we are currently dragging an item */ isDragging: boolean = false; /** Store the group of the dragged item */ private _group: string; /** Ensure we destroy all subscriptions */ private readonly _onDestroy = new Subject(); constructor() { // subscribe to drag events this._dragService.onDragStart .pipe( tap(event => (this._group = event.group)), filter(event => this.isDropAllowed(event.group)), takeUntil(this._onDestroy) ) .subscribe(this.onDragStart.bind(this)); this._dragService.onDragEnd .pipe( filter(event => this.isDropAllowed(event.group)), takeUntil(this._onDestroy) ) .subscribe(this.onDragEnd.bind(this)); } ngOnDestroy(): void { this._onDestroy.next(); this._onDestroy.complete(); } /** Update the mouse over state */ @HostListener('mouseenter') onMouseOver(): void { if (this.isDropAllowed(this._group)) { this.isMouseOver = true; // emit that we are over a drop area this._dragService.onDropEnter.next(); } } /** Update the mouse over state */ @HostListener('mouseleave') onMouseLeave(): void { // always ensure this value is reset this.isMouseOver = false; // only emit the dropd leave event when appropriate if (this.isDropAllowed(this._group)) { this._dragService.onDropLeave.next(); } } /** Update the dragging state */ onDragStart(): void { this.isDragging = true; } /** Update the dragging state */ onDragEnd(event: UxDragEvent): void { // update the dragging state this.isDragging = false; // clear the cached group this._group = null; // if the mouse is over and it is in an allowed group emit the dop event if (this.isMouseOver && this.isDropAllowed(event.group)) { this.onDrop.emit(event.data); this._dragService.onDrop.next(event.data); } } /** Determine whether or not the event is part of the specified groups */ private isDropAllowed(group: string): boolean { // if dropping is disabled then it is never allowed if (this.dropDisabled) { return false; } // if no group specified allow all groups if (!this.group) { return true; } // if it is an array then ensure it is allowed if (Array.isArray(this.group)) { return !!this.group.find(_group => _group === group); } return this.group === group; } }