@progress/kendo-angular-sortable
Version:
A Sortable Component for Angular
235 lines (234 loc) • 8.43 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Injectable, NgZone } from '@angular/core';
import { isDocumentAvailable } from '@progress/kendo-angular-common';
import { draggableFromEvent, isFocusable, widgetTarget } from './util';
import { Subject } from 'rxjs';
import { switchMap, filter, take, tap } from 'rxjs/operators';
import * as i0 from "@angular/core";
const allowDrag = (e) => {
const target = e.originalEvent.target;
return target.hasAttribute('data-sortable-item') || !(isFocusable(target) || widgetTarget(target));
};
/**
* The service that provides the drag-and-drop functionality for
* transferring items between Sortable components within the same page.
*
*/
export class SortableService {
ngZone;
/**
* Specifies the Draggable item that is currently being moved.
*/
activeDraggable = null;
/**
* Specifies the Draggable item from which the dragging started.
*/
originDraggable = null;
/**
* @hidden
*/
originIndex;
/**
* @hidden
*/
targetSortable = null;
/**
* Specifies the Draggable item that last emitted an event.
*/
lastDraggable = null;
/**
* @hidden
*/
onPressSubject = new Subject();
/**
* @hidden
*/
onDragSubject = new Subject();
/**
* @hidden
*/
onReleaseSubject = new Subject();
subscriptions;
source = null;
_target = null;
sortableCounter = 0;
sortableRegister = {};
pressArgs;
/**
* Specifies the `SortableComponent` instance under the currently dragged item.
*/
set target(target) {
this._target = target;
}
get target() {
return this._target;
}
constructor(ngZone) {
this.ngZone = ngZone;
if (!isDocumentAvailable()) {
return;
}
this.subscriptions = this.onPressSubject.pipe(filter(allowDrag), tap(press => {
this.targetSortable = this.getSortableComponentFromTouch(press);
}), filter(_ => Boolean(this.targetSortable)), tap(press => {
this.onReleaseSubject.pipe(take(1)).subscribe(event => this.release(event));
this.pressArgs = press;
if (press.isTouch) {
press.originalEvent.preventDefault();
}
}), switchMap(_drag => this.onDragSubject.pipe(filter(_ => Boolean(this.targetSortable)), //stop further events if dragStart is prevented
tap((e) => this.drag(e))))).subscribe();
}
/**
* @hidden
*/
onPress(e) {
this.onPressSubject.next(e);
}
/**
* @hidden
*/
onDrag(e) {
this.onDragSubject.next(e);
}
/**
* @hidden
*/
onRelease(e) {
this.onReleaseSubject.next(e);
}
/**
* @hidden
*/
ngOnDestroy() {
if (this.subscriptions) {
this.subscriptions.unsubscribe();
}
}
/**
* Registers a `SortableComponent` with which the service operates.
*
* @param sortableComponent - The `SortableComponent`.
* @return - The unique key that the current `SortableComponent` gets when registered.
*/
registerComponent(sortableComponent) {
const id = this.sortableCounter.toString();
this.sortableRegister[id] = sortableComponent;
this.sortableCounter++;
return id;
}
/**
* Removes a `SortableComponent` from the registered `SortableComponents` with which the service operates.
*
* @param key - The key of the `SortableComponent` which will be removed from the register.
* Obtained when `registerComponent` is called.
*/
unregisterComponent(key) {
this.sortableRegister[key] = null;
}
/**
* Sets the `SortableComponent` as a source component. When dragging an item from one Sortable to another,
* the source component is the one from which the item originates.
*
* @param sortable - The `SortableComponent`.
*/
setSource(sortable) {
this.source = sortable;
}
/**
* Returns the source `SortableComponent` from which
* an item is dragged to other Sortable components.
*
* @return - The `SourceComponent`.
*/
getSource() {
return this.source;
}
/**
* The method that finds the `SortableComponent` which is registered to
* the `SortableService` by using the arguments of the `touch` event.
*
* @param touch - A Touch-Object of the `Touch` type interface.
* Represents a single contact point (finger or stylus)
* on a touch-sensitive device (touchscreen or trackpad).
*
* @return { component: SortableComponent, index: number } - An object
* where the component is the `SortableComponent` that owns the item
* and the index is the index of the touched item.
*/
getSortableComponentFromTouch(touch) {
if (!isDocumentAvailable()) {
return { component: undefined, index: undefined };
}
let realTarget = document.elementFromPoint(touch.clientX, touch.clientY);
while (realTarget) {
const id = realTarget.getAttribute('data-sortable-id');
const index = realTarget.getAttribute('data-sortable-index');
if (id) {
const targetSortable = this.sortableRegister[id];
if (targetSortable) {
return { component: targetSortable, index: parseInt(index, 10) };
}
}
realTarget = realTarget.parentElement;
}
}
start() {
const pressArgs = this.pressArgs;
if (pressArgs) {
this.pressArgs = null;
const startTarget = draggableFromEvent(pressArgs, this.targetSortable.component);
if (this.targetSortable.component.startDrag({ target: startTarget, originalEvent: pressArgs })) {
this.targetSortable = null;
return true;
}
}
}
release(event) {
if (this.source) {
this.ngZone.run(() => {
if (this.targetSortable) {
const dropTarget = draggableFromEvent(event, this.targetSortable.component);
this.source.endDrag({ target: dropTarget, originalEvent: event });
}
this.source.positionHintFromEvent(null);
this.source.markForCheck();
});
}
this.targetSortable = null;
this.pressArgs = null;
}
drag(event) {
this.ngZone.run(() => {
if (this.start()) {
return;
}
this.source.positionHintFromEvent(event);
const sortable = this.getSortableComponentFromTouch(event);
if (!sortable || sortable && sortable.component !== this.target) {
if (this.target) {
this.target.leave({ target: undefined, originalEvent: event });
}
else if (this.source !== this.target) {
this.source.leave({ target: undefined, originalEvent: event });
}
}
if (sortable && sortable.component) {
const draggable = draggableFromEvent(event, sortable.component);
sortable.component.drag({ target: draggable, originalEvent: event });
}
this.source.markForCheck();
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SortableService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SortableService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SortableService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; } });