UNPKG

@progress/kendo-angular-utils

Version:

Kendo UI Angular utils component

1,366 lines (1,353 loc) 95 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import * as i0 from '@angular/core'; import { Directive, HostBinding, Input, Injectable, Component, isDevMode, EventEmitter, Output, ContentChildren, NgModule, InjectionToken, Optional, Inject } from '@angular/core'; import { validatePackage } from '@progress/kendo-licensing'; import { dispatchDragAndDrop, getScrollableParent, autoScroll } from '@progress/kendo-draggable-common'; import { PreventableEvent, contains, isDocumentAvailable, parseCSSClassNames, isPresent as isPresent$1, areObjectsEqual } from '@progress/kendo-angular-common'; import { NgTemplateOutlet } from '@angular/common'; import { Subject, BehaviorSubject, Subscription, fromEvent } from 'rxjs'; import { map, tap, filter } from 'rxjs/operators'; /** * Represents the Kendo UI DragHandle directive for Angular. * It is used to specify a concrete element within a drag target as a handle for dragging, instead the drag target itself. */ class DragHandleDirective { element; touchActionStyle = 'none'; /** * Specifies the cursor style of the drag handle. Accepts same values as the [CSS `cursor` property](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values). * * @default 'move' */ cursorStyle = 'move'; constructor(element) { this.element = element; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragHandleDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: DragHandleDirective, isStandalone: true, selector: "[kendoDragHandle]", inputs: { cursorStyle: "cursorStyle" }, host: { properties: { "style.touch-action": "this.touchActionStyle", "style.cursor": "this.cursorStyle" } }, exportAs: ["kendoDragHandle"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragHandleDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoDragHandle]', exportAs: 'kendoDragHandle', standalone: true }] }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { touchActionStyle: [{ type: HostBinding, args: ['style.touch-action'] }], cursorStyle: [{ type: HostBinding, args: ['style.cursor'] }, { type: Input }] } }); /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-utils', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1749540010, version: '19.1.1', licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/' }; function isDocumentNode(container) { return container.nodeType === 9; } /** * @hidden */ const getAction = (event, draggable) => { return { event: event, payload: draggable }; }; /** * @hidden */ const dragTargetTransition = 'transform .3s ease-in-out'; /** * @hidden */ const isPresent = (value) => value !== null && value !== undefined; /** * @hidden */ function closestBySelector(element, selector) { if (element.closest) { return element.closest(selector); } const matches = Element.prototype.matches ? (el, sel) => el.matches(sel) : (el, sel) => el.msMatchesSelector(sel); let node = element; while (node && !isDocumentNode(node)) { if (matches(node, selector)) { return node; } node = node.parentNode; } } /** * @hidden */ const intersect = (element, candidates) => { let max = 0; let result = null; candidates.forEach((candidate) => { if (candidate && element) { const ration = getRatio(element, candidate); if (ration > max) { max = ration; result = candidate; } } }); return result; }; const getRatio = (element, target) => { const elementRect = element.getBoundingClientRect(); const targetRect = target.getBoundingClientRect(); const top = Math.max(targetRect.top, elementRect.top); const left = Math.max(targetRect.left, elementRect.left); const right = Math.min(targetRect.left + targetRect.width, elementRect.left + elementRect.width); const bottom = Math.min(targetRect.top + targetRect.height, elementRect.top + elementRect.height); const width = right - left; const height = bottom - top; if (left < right && top < bottom) { const targetArea = targetRect.width * targetRect.height; const entryArea = elementRect.width * elementRect.height; const intersectionArea = width * height; const intersectionRatio = intersectionArea / (targetArea + entryArea - intersectionArea); return Number(intersectionRatio.toFixed(4)); } return 0; }; /** * @hidden */ const setElementStyles = (renderer, elem, styles) => { const props = Object.keys(styles); props.forEach(p => { renderer.setStyle(elem, p, styles[p]); }); }; /** * @hidden */ const noop = () => { }; /** * @hidden */ class DragStateService { constructor() { this.setCallbacks(); } dragTarget = null; dropTarget = null; dragTargets = []; dropTargets = []; pressed = false; ignoreMouse = false; autoScroll = true; isScrolling = false; scrollableParent = null; autoScrollDirection = { horizontal: true, vertical: true }; initialClientOffset = { x: 0, y: 0 }; clientOffset = { x: 0, y: 0 }; initialScrollOffset = { x: 0, y: 0 }; scrollOffset = { x: 0, y: 0 }; offset = { x: 0, y: 0 }; pageOffset = { x: 0, y: 0 }; velocity = { x: 0, y: 0 }; dragTargetDirective; state; dragIndex = null; dropIndex = null; dragData; dragTargetId; callbacks = {}; scrollInterval = null; handleDragAndDrop(action) { this.updateState(); dispatchDragAndDrop(this.state, action, this.callbacks); } setPressed(pressed) { this.pressed = pressed; } setScrolling(isScrolling) { this.isScrolling = isScrolling; if (isScrolling) { const scrollableParent = getScrollableParent(document.elementFromPoint(this.clientOffset.x, this.clientOffset.y)); window.clearInterval(this.scrollInterval); this.scrollInterval = window.setInterval(() => { autoScroll(scrollableParent, { x: this.velocity.x, y: this.velocity.y }); }, 50); } else { if (this.scrollInterval) { window.clearInterval(this.scrollInterval); this.scrollInterval = null; } } } setVelocity(velocity) { this.velocity = velocity; } setOffset(offset) { this.offset = offset; } setClientOffset(clientOffset) { this.clientOffset = clientOffset; } setPageOffset(pageOffset) { this.pageOffset = pageOffset; } setInitialClientOffset(initialClientOffset) { this.initialClientOffset = initialClientOffset; } setScrollOffset(scrollOffset) { this.scrollOffset = scrollOffset; } setInitialScrollOffset(initialScrollOffset) { this.initialScrollOffset = initialScrollOffset; } get dragTargetPresent() { return isPresent(this.dragTarget?.element); } get dropTargetPresent() { return isPresent(this.dropTarget?.element); } updateState() { this.state = { drag: this.dragTarget, drop: this.dropTarget, drags: this.dragTargets, drops: this.dropTargets, pressed: this.pressed, ignoreMouse: this.ignoreMouse, autoScroll: this.autoScroll, isScrolling: this.isScrolling, scrollableParent: this.scrollableParent, autoScrollDirection: this.autoScrollDirection, initialClientOffset: this.initialClientOffset, clientOffset: this.clientOffset, initialScrollOffset: this.initialScrollOffset, scrollOffset: this.scrollOffset, offset: this.offset, pageOffset: this.pageOffset, velocity: this.velocity }; } setCallbacks() { this.callbacks = { onVelocityChange: this.setVelocity.bind(this), onOffsetChange: this.setOffset.bind(this), onClientOffsetChange: this.setClientOffset.bind(this), onPageOffsetChange: this.setPageOffset.bind(this), onInitialClientOffsetChange: this.setInitialClientOffset.bind(this), onScrollOffsetChange: this.setScrollOffset.bind(this), onInitialScrollOffsetChange: this.setInitialScrollOffset.bind(this), onIsPressedChange: this.setPressed.bind(this), onIsScrollingChange: this.setScrolling.bind(this) }; } ngOnDestroy() { if (this.scrollInterval) { window.clearInterval(this.scrollInterval); this.scrollInterval = null; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragStateService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragStateService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return []; } }); /** * @hidden */ class HintComponent { element; template; directive; targetIndex; contextData; customContext; pointerEvents = 'none'; constructor(element) { this.element = element; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HintComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HintComponent, isStandalone: true, selector: "kendo-draghint", inputs: { template: "template", directive: "directive", targetIndex: "targetIndex", contextData: "contextData", customContext: "customContext" }, host: { properties: { "style.pointer-events": "this.pointerEvents" } }, ngImport: i0, template: ` <ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="customContext || { $implicit: this.directive, index: this.targetIndex, data: this.contextData }"> </ng-container> `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HintComponent, decorators: [{ type: Component, args: [{ selector: 'kendo-draghint', template: ` <ng-container [ngTemplateOutlet]="template" [ngTemplateOutletContext]="customContext || { $implicit: this.directive, index: this.targetIndex, data: this.contextData }"> </ng-container> `, standalone: true, imports: [NgTemplateOutlet] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { template: [{ type: Input }], directive: [{ type: Input }], targetIndex: [{ type: Input }], contextData: [{ type: Input }], customContext: [{ type: Input }], pointerEvents: [{ type: HostBinding, args: ['style.pointer-events'] }] } }); /** * Arguments for the press event of the DragTarget and DragTargetContainer. */ class DragTargetPressEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get normalizedEvent() { return this.dragEvent; } /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get hostElement() { return this.dragTarget; } /** * @hidden */ constructor(args) { Object.assign(this, args); } } /** * Arguments for the dragReady event of the DragTarget and DragTargetContainer. */ class DragTargetDragReadyEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * @hidden */ constructor(args) { Object.assign(this, args); } } /** * Arguments for the dragStart event of the DragTarget and DragTargetContainer. */ class DragTargetDragStartEvent extends PreventableEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get normalizedEvent() { return this.dragEvent; } /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get hostElement() { return this.dragTarget; } /** * @hidden */ constructor(args) { super(); Object.assign(this, args); } } /** * Arguments for the dragEnd event of the DragTarget and DragTargetContainer. */ class DragTargetDragEndEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get normalizedEvent() { return this.dragEvent; } /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get hostElement() { return this.dragTarget; } /** * @hidden */ constructor(args) { Object.assign(this, args); } } /** * Arguments for the drag event of the DragTarget and DragTargetContainer. */ class DragTargetDragEvent extends PreventableEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The hint of the DragTarget. */ hintElement; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get normalizedEvent() { return this.dragEvent; } /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get hostElement() { return this.dragTarget; } /** * @hidden */ constructor(args) { super(); Object.assign(this, args); } } /** * Arguments for the release event of the DragTarget and DragTargetContainer. */ class DragTargetReleaseEvent { /** * The normalized drag event. */ dragEvent; /** * The DOM element that is being dragged. */ dragTarget; /** * The identifier passed to the `dragTargetId` input property of the `DragTarget` or `DragTargetContainer` directive. */ dragTargetId; /** * The index of the current drag target in the collection of drag targets (applicable for the `DragTargetContainer` directive). */ dragTargetIndex; /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get normalizedEvent() { return this.dragEvent; } /** * Left for backward compatibility for the DragTarget deprecated events. * @hidden */ get hostElement() { return this.dragTarget; } /** * @hidden */ constructor(args) { Object.assign(this, args); } } let isDragStartPrevented$1 = false; let isDragPrevented$1 = false; /** * Represents the [Kendo UI DragTargetContainer directive for Angular]({% slug api_utils_dragtargetcontainerdirective %}). * Used to configure multiple elements as draggable. * * @example * ```ts-no-run * <ul kendoDragTargetContainer dragTargetFilter=".my-draggable"> * <li class="my-draggable">foo</li> * </ul> * ``` */ class DragTargetContainerDirective { wrapper; ngZone; renderer; service; viewContainer; cdr; /** * Defines whether a hint will be used for dragging. By default, the hint is a copy of the current drag target. ([see example]({% slug drag_hint %})). * * @default false */ hint = false; /** * Specifies a selector for elements within a container which will be configured as draggable * ([see example]({% slug drag_target_container %})). The possible values include any * DOM `selector`. */ set dragTargetFilter(value) { this._dragTargetFilter = value; if (!this.dragDisabled) { this.initializeDragTargets(); } } get dragTargetFilter() { return this._dragTargetFilter; } /** * Specifies a selector for elements within each DragTarget which will be configured as drag handles. */ dragHandle; /** * Defines the delay in milliseconds after which the drag will begin ([see example](slug:drag_target_container#toc-events)). * * @default 0 */ dragDelay = 0; /** * The number of pixels the pointer moves in any direction before the dragging starts ([see example]({% slug minimum_distance %})). * * @default 0 */ threshold = 0; /** * Defines a unique identifier for each drag target. * It exposes the current DragTarget HTML element and its index in the collection of drag targets as arguments. */ set dragTargetId(fn) { if (isDevMode && typeof fn !== 'function') { throw new Error(`dragTargetId must be a function, but received ${JSON.stringify(fn)}.`); } this._dragTargetId = fn; } get dragTargetId() { return this._dragTargetId; } /** * Defines a callback function which returns custom data passed to the DropTarget events. * It exposes the current DragTarget HTML element, its `dragTargetId` and its index in the collection of drag targets as arguments. */ set dragData(fn) { if (isDevMode && typeof fn !== 'function') { throw new Error(`dragData must be a function, but received ${JSON.stringify(fn)}.`); } this._dragData = fn; } get dragData() { return this._dragData; } /** * If set to true, the dragging of DragTargets within the container will be disabled. * * @default false */ set dragDisabled(value) { this._dragDisabled = value; if (value) { this.clearPreviousTargets(); this.removeListeners(); if (isPresent(this.hintElem)) { this.destroyHint(); } } else { if (isPresent(this.wrapper) || isPresent(this.currentDragTarget)) { this.subscribe(); } this.initializeDragTargets(); } } get dragDisabled() { return this._dragDisabled; } /** * Specifies whether the default dragging behavior will be performed or the developer will manually handle the drag action. * * @default 'auto' */ mode = 'auto'; /** * Specifies the cursor style of the drag targets. Accepts same values as the [CSS `cursor` property](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values). * * @default 'move' */ cursorStyle = 'move'; /** * @hidden */ hintContext; /** * Fires when a DragTarget's `dragDelay` has passed and the user is able to drag the element. */ onDragReady = new EventEmitter(); /** * Fires when the user presses a DragTarget element. */ onPress = new EventEmitter(); /** * Fires when the dragging of a DragTarget element begins. */ onDragStart = new EventEmitter(); /** * Fires while the user drags a DragTarget element. */ onDrag = new EventEmitter(); /** * Fires when the user releases a DragTarget element after being pressed. */ onRelease = new EventEmitter(); /** * Fires when the dragging of a DragTarget ends and the element is released. */ onDragEnd = new EventEmitter(); /** * Used for notifying the DragTargetContainer that its content has changed. */ notify() { this.cdr.detectChanges(); this.initializeDragTargets(); } currentDragTarget = null; dragTimeout = null; pressed = false; dragStarted = false; hintComponent = null; defaultHint = null; currentDragTargetElement = null; scrollableParent = null; previousDragTargets = []; initialPosition = { x: 0, y: 0 }; position = { x: 0, y: 0 }; positionsMap = new Map(); _dragTargetFilter = null; _dragDisabled = false; _dragData = () => null; _dragTargetId = () => null; prevUserSelect; get allDragTargets() { return this.queryHost(this.dragTargetFilter); } get dragHandles() { return this.isHandleSelectorValid ? this.queryHost(this.dragHandle) : null; } get hintTemplate() { return isPresent(this.hint) && typeof this.hint === 'object' ? this.hint.hintTemplate : null; } constructor(wrapper, ngZone, renderer, service, viewContainer, cdr) { this.wrapper = wrapper; this.ngZone = ngZone; this.renderer = renderer; this.service = service; this.viewContainer = viewContainer; this.cdr = cdr; validatePackage(packageMetadata); } ngAfterViewInit() { const isTargetPresent = isPresent(this.wrapper) || isPresent(this.currentDragTarget); if (!this.dragDisabled && isTargetPresent) { this.subscribe(); } !this.dragDisabled && this.initializeDragTargets(); } ngOnDestroy() { this.removeListeners(); } onPointerDown(event) { const filterElement = closestBySelector(event.target, this.isHandleSelectorValid ? this.dragHandle : this.dragTargetFilter); if (this.dragTargetFilter === '' || !isPresent(filterElement)) { return; } if (isPresent(this.dragHandles) && !this.isDragHandle(event.target)) { return; } const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); this.subscribe(); } onTouchStart(event) { const filterElement = closestBySelector(event.target, this.isHandleSelectorValid ? this.dragHandle : this.dragTargetFilter); if (this.dragTargetFilter === '' || !isPresent(filterElement)) { return; } if (isPresent(this.dragHandles) && !this.isDragHandle(event.target)) { return; } event.preventDefault(); const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); this.subscribe(); } onPointerMove(event) { const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); } onTouchMove(event) { event.preventDefault(); const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); } onPointerUp(event) { const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); this.subscribe(); } onContextMenu(event) { event.preventDefault(); const action = getAction(event, this.currentDragTarget); this.service.handleDragAndDrop(action); this.subscribe(); } handlePress(event) { if (this.dragDelay > 0) { this.dragTimeout = window.setTimeout(() => { this.pressed = true; this.emitZoneAwareEvent('onDragReady', event); }, this.dragDelay); } else { this.pressed = true; } const eventTarget = event.originalEvent.target; this.currentDragTargetElement = closestBySelector(eventTarget, this.dragTargetFilter); this.currentDragTarget.element = this.currentDragTargetElement; this.service.dragIndex = this.getDragIndex(); this.scrollableParent = this.hintTemplate ? document.body : this.currentDragTargetElement ? getScrollableParent(this.currentDragTargetElement) : null; this.prevUserSelect = this.currentDragTargetElement.style.userSelect; this.renderer.setStyle(this.currentDragTargetElement, 'user-select', 'none'); this.emitZoneAwareEvent('onPress', event); } handleDragStart(event) { if (!this.pressed) { if (this.dragTimeout) { window.clearTimeout(this.dragTimeout); this.dragTimeout = null; } return; } isDragStartPrevented$1 = this.emitZoneAwareEvent('onDragStart', event).isDefaultPrevented(); if (isDragStartPrevented$1) { return; } this.position = this.positionsMap.has(this.currentDragTargetElement) ? this.positionsMap.get(this.currentDragTargetElement) : { x: 0, y: 0 }; if (this.hint) { this.createHint(); if (this.mode === 'auto') { this.renderer.setStyle(this.currentDragTargetElement, 'opacity', '0.7'); } } else { this.initialPosition = { x: event.clientX - this.position.x, y: event.clientY - this.position.y }; } this.dragStarted = this.threshold === 0; this.service.dragTarget = this.currentDragTarget; const targetIdArgs = { dragTarget: this.currentDragTargetElement, dragTargetIndex: this.service.dragIndex }; this.service.dragTargetId = this.dragTargetId(targetIdArgs); const targetDataArgs = Object.assign({ dragTargetId: this.service.dragTargetId }, targetIdArgs); this.service.dragData = this.dragData(targetDataArgs); } handleDrag(event) { if (!this.pressed || isDragStartPrevented$1) { return; } const elem = this.hint ? this.hintElem : this.currentDragTargetElement; this.position = this.calculatePosition(elem, event); const thresholdNotReached = Math.abs(this.position.x) < this.threshold && Math.abs(this.position.y) < this.threshold; if (!this.dragStarted && thresholdNotReached) { return; } if (!this.dragStarted && this.threshold > 0) { this.dragStarted = true; } isDragPrevented$1 = this.emitZoneAwareEvent('onDrag', event).isDefaultPrevented(); if (isDragPrevented$1) { return; } if (this.mode === 'auto') { this.performDrag(); } else { this.dragStarted = true; } } handleRelease(event) { if (this.dragStarted) { this.positionsMap.set(this.currentDragTargetElement, this.position); } if (this.dragTimeout) { clearTimeout(this.dragTimeout); this.dragTimeout = null; } this.pressed = false; this.prevUserSelect ? this.renderer.setStyle(this.currentDragTargetElement, 'user-select', this.prevUserSelect) : this.renderer.removeStyle(this.currentDragTargetElement, 'user-select'); this.prevUserSelect = null; this.emitZoneAwareEvent('onRelease', event); } handleDragEnd(event) { if (!this.dragStarted) { return; } if (this.mode === 'auto') { const isDroppedOverParentTarget = isPresent(this.service.dropTarget) && !contains(this.service.dropTarget?.element, this.service.dragTarget?.element, true); const elem = this.hint ? this.hintElem : this.currentDragTargetElement; if (isDroppedOverParentTarget || this.service.dropTargets.length > 0 && isPresent(elem)) { this.renderer.removeStyle(elem, 'transform'); setElementStyles(this.renderer, elem, { transition: dragTargetTransition }); this.positionsMap.delete(this.currentDragTargetElement); } } if (this.hint && isPresent(this.hintElem)) { this.destroyHint(); if (this.mode === 'auto') { this.renderer.removeStyle(this.currentDragTargetElement, 'opacity'); } } this.service.dragTarget = null; this.service.dragIndex = null; this.currentDragTarget.element = null; this.emitZoneAwareEvent('onDragEnd', event); if (isDragStartPrevented$1 || isDragPrevented$1) { return; } this.dragStarted = false; } get nativeElement() { return this.wrapper.nativeElement; } get hintElem() { return this.hintTemplate && isPresent(this.hintComponent) ? this.hintComponent.instance.element.nativeElement : this.defaultHint; } removeListeners() { if (isPresent(this.scrollableParent)) { this.scrollableParent.removeEventListener('scroll', this.onPointerMove); } const element = this.nativeElement; if (!isDocumentAvailable()) { return; } document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp, true); document.removeEventListener('pointercancel', this.onPointerUp); document.removeEventListener('contextmenu', this.onContextMenu); window.removeEventListener('touchmove', noop); element.removeEventListener('touchmove', this.onTouchMove); element.removeEventListener('touchend', this.onPointerUp); document.removeEventListener('mousemove', this.onPointerMove); document.removeEventListener('mouseup', this.onPointerUp); document.removeEventListener('touchcancel', this.onPointerUp); element.removeEventListener('pointerdown', this.onPointerDown); element.removeEventListener('mousedown', this.onPointerDown); element.removeEventListener('touchstart', this.onTouchStart); } get supportPointerEvent() { return Boolean(typeof window !== 'undefined' && window.PointerEvent); } subscribe() { this.ngZone.runOutsideAngular(() => { this.removeListeners(); if (!(isDocumentAvailable() && isPresent(this.wrapper))) { return; } this.onPointerMove = this.onPointerMove.bind(this); this.onPointerUp = this.onPointerUp.bind(this); this.onTouchMove = this.onTouchMove.bind(this); this.onContextMenu = this.onContextMenu.bind(this); this.onPointerDown = this.onPointerDown.bind(this); this.onTouchStart = this.onTouchStart.bind(this); const element = this.nativeElement; if (this.supportPointerEvent) { if (isPresent(this.scrollableParent)) { this.scrollableParent.addEventListener('scroll', this.onPointerMove, { passive: true }); } element.addEventListener('pointerdown', this.onPointerDown, { passive: true }); if (this.pressed) { document.addEventListener('pointermove', this.onPointerMove); document.addEventListener('pointerup', this.onPointerUp, true); document.addEventListener('contextmenu', this.onContextMenu); document.addEventListener('pointercancel', this.onPointerUp, { passive: true }); } } else { window.addEventListener('touchmove', noop, { capture: false, passive: false }); element.addEventListener('mousedown', this.onPointerDown, { passive: true }); element.addEventListener('touchstart', this.onTouchStart, { passive: true }); if (this.pressed) { document.addEventListener('mousemove', this.onPointerMove, { passive: true }); document.addEventListener('mouseup', this.onPointerUp, { passive: true }); element.addEventListener('touchmove', this.onTouchMove, { passive: true }); element.addEventListener('touchend', this.onPointerUp, { passive: true }); } } }); } emitZoneAwareEvent(event, normalizedEvent) { const targetIdArgs = { dragTarget: this.currentDragTargetElement, dragTargetIndex: this.service.dragIndex }; const eventProps = { dragTarget: this.currentDragTargetElement, dragEvent: normalizedEvent, dragTargetIndex: this.service.dragIndex, dragTargetId: this.dragTargetId(targetIdArgs) }; if (this.hint && isPresent(this.hintElem)) { eventProps.hintElement = this.hintElem; } let eventArgs; switch (event) { case 'onDragReady': eventArgs = new DragTargetDragReadyEvent(eventProps); break; case 'onPress': eventArgs = new DragTargetPressEvent(eventProps); break; case 'onDragStart': eventArgs = new DragTargetDragStartEvent(eventProps); break; case 'onDrag': eventArgs = new DragTargetDragEvent(eventProps); break; case 'onRelease': eventArgs = new DragTargetReleaseEvent(eventProps); break; case 'onDragEnd': eventArgs = new DragTargetDragEndEvent(eventProps); break; default: break; } this.ngZone.run(() => { this[event].emit(eventArgs); }); return eventArgs; } createHint() { if (!(isDocumentAvailable() && isPresent(this.wrapper))) { return; } if (isPresent(this.hint) && typeof this.hint === 'object') { if (isPresent(this.hint.hintTemplate)) { this.createCustomHint(); } else { this.createDefaultHint(); } } else { this.createDefaultHint(); } this.currentDragTarget.hint = this.hintElem; if (typeof this.hint === 'object' && isPresent(this.hint.appendTo)) { this.hint.appendTo.element.nativeElement.appendChild(this.hintElem); } else { document.body.appendChild(this.hintElem); } } createDefaultHint() { this.defaultHint = this.currentDragTargetElement.cloneNode(true); if (typeof this.hint === 'object') { if (isPresent(this.hint.hintClass)) { const hintClasses = parseCSSClassNames(this.hint.hintClass); hintClasses.forEach(className => this.renderer.addClass(this.defaultHint, className)); } } } createCustomHint() { if (isPresent(this.hint.appendTo)) { this.hintComponent = this.hint.appendTo.createComponent(HintComponent); } else { this.hintComponent = this.viewContainer.createComponent(HintComponent); } this.hintComponent.instance.template = this.hintTemplate; this.hintComponent.instance.directive = this; this.hintComponent.instance.targetIndex = this.service.dragIndex; const targetDataArgs = { dragTarget: this.currentDragTargetElement, dragTargetId: this.service.dragTargetId, dragTargetIndex: this.service.dragIndex }; this.hintComponent.instance.contextData = this.dragData(targetDataArgs); this.hintComponent.instance.customContext = this.hintContext; this.hintComponent.changeDetectorRef.detectChanges(); } destroyHint() { if (isPresent(this.hintTemplate)) { this.hintComponent.destroy(); this.hintComponent.changeDetectorRef.detectChanges(); this.hintComponent = null; } else { document.body.removeChild(this.defaultHint); this.defaultHint = null; } this.currentDragTarget.hint = null; } getDragIndex() { return this.allDragTargets.indexOf(this.currentDragTargetElement); } initializeDragTargets() { if (!isPresent(this.allDragTargets)) { if (this.previousDragTargets.length > 0) { this.clearPreviousTargets(); } return; } this.allDragTargets.forEach(dragTargetEl => { const isDragTargetInitialized = this.service.dragTargets.find(dt => dt.element === dragTargetEl); if (!isDragTargetInitialized) { this.service.dragTargets.push({ element: dragTargetEl, hint: null, onPress: this.handlePress.bind(this), onRelease: this.handleRelease.bind(this), onDragStart: this.handleDragStart.bind(this), onDrag: this.handleDrag.bind(this), onDragEnd: this.handleDragEnd.bind(this) }); } }); if (this.previousDragTargets.length > 0) { const dragTargetsToRemove = this.previousDragTargets.filter(dt => !this.allDragTargets.includes(dt)); dragTargetsToRemove.forEach(dragTarget => { const idx = this.service.dragTargets.findIndex(serviceDragTarget => serviceDragTarget.element === dragTarget); if (idx > -1) { this.service.dragTargets.splice(idx, 1); } }); } this.previousDragTargets = this.allDragTargets; this.currentDragTarget = { element: null, hint: null, onPress: this.handlePress.bind(this), onRelease: this.handleRelease.bind(this), onDragStart: this.handleDragStart.bind(this), onDrag: this.handleDrag.bind(this), onDragEnd: this.handleDragEnd.bind(this) }; this.setTargetStyles(); } isDragHandle(el) { return this.dragHandles.some(dh => contains(dh, el, true)); } get isHandleSelectorValid() { return isPresent(this.dragHandle) && this.dragHandle !== ''; } setTargetStyles() { if (!isDocumentAvailable()) { return; } if (isPresent(this.dragHandle) && this.dragHandle !== '') { if (isPresent(this.dragHandles) && this.dragHandles.length > 0) { this.dragHandles.forEach(handle => { this.renderer.setStyle(handle, 'cursor', this.cursorStyle); this.renderer.setStyle(handle, 'touch-action', 'none'); }); } } else { this.allDragTargets.forEach(target => { this.renderer.setStyle(target, 'cursor', this.cursorStyle); this.renderer.setStyle(target, 'touch-action', 'none'); }); } } queryHost(selector) { if (isPresent(selector) && selector !== "") { return Array.from(this.nativeElement.querySelectorAll(selector)); } } clearPreviousTargets() { this.previousDragTargets.forEach(dragTarget => { const idx = this.service.dragTargets.findIndex(serviceDragTarget => serviceDragTarget.element === dragTarget); if (idx > -1) { this.service.dragTargets.splice(idx, 1); } }); this.previousDragTargets = []; } performDrag() { const elem = this.hint ? this.hintElem : this.currentDragTargetElement; if (elem) { const styles = this.getStylesPerElement(elem); setElementStyles(this.renderer, elem, styles); } } calculatePosition(element, event) { let position = null; if (!isDocumentAvailable()) { return { x: 0, y: 0 }; } if (element === this.hintElem) { position = { x: event.clientX + window.scrollX, y: event.clientY + window.scrollY }; } else { position = { x: event.clientX - this.initialPosition.x + event.scrollX, y: event.clientY - this.initialPosition.y + event.scrollY }; } return position; } getStylesPerElement(element) { if (element === this.hintElem) { return { top: `${this.position.y}px`, left: `${this.position.x}px`, transition: 'none', position: 'absolute', zIndex: 1999 }; } else { const transform = `translate(${this.position.x}px, ${this.position.y}px)`; return { transform: transform, transition: 'none' }; } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragTargetContainerDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: DragStateService }, { token: i0.ViewContainerRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: DragTargetContainerDirective, isStandalone: true, selector: "[kendoDragTargetContainer]", inputs: { hint: "hint", dragTargetFilter: "dragTargetFilter", dragHandle: "dragHandle", dragDelay: "dragDelay", threshold: "threshold", dragTargetId: "dragTargetId", dragData: "dragData", dragDisabled: "dragDisabled", mode: "mode", cursorStyle: "cursorStyle", hintContext: "hintContext" }, outputs: { onDragReady: "onDragReady", onPress: "onPress", onDragStart: "onDragStart", onDrag: "onDrag", onRelease: "onRelease", onDragEnd: "onDragEnd" }, exportAs: ["kendoDragTargetContainer"], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragTargetContainerDirective, decorators: [{ type: Directive, args: [{ selector: '[kendoDragTargetContainer]', exportAs: 'kendoDragTargetContainer', standalone: true }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: DragStateService }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { hint: [{ type: Input }], dragTargetFilter: [{ type: Input }], dragHandle: [{ type: Input }], dragDelay: [{ type: Input }], threshold: [{ type: Input }], dragTargetId: [{ type: Input }], dragData: [{ type: Input }], dragDisabled: [{ type: Input }], mode: [{ type: Input }], cursorStyle: [{ type: Input }], hintContext: [{ type: Input }], onDragReady: [{ type: Output }], onPress: [{ type: Output }], onDragStart: [{ type: Output }], onDrag: [{ type: Output }], onRelease: [{ type: Output }], onDragEnd: [{ type: Output }] } }); let isDragStartPrevented = false; let isDragPrevented = false; /** * Represents the Kendo UI DragTarget directive for Angular. */ class DragTargetDirective { element; renderer; ngZone; service; viewContainer; get touchActionStyle() { return this.dragHandles.length > 0 ? null : 'none'; } /** * Defines whether a hint will be used for dragging. By default, the hint is a copy of the drag target. ([see example]({% slug drag_hint %})). * * @default false */ hint = false; /** * The number of pixels the pointer moves in any direction before the dragging starts ([see example]({% slug minimum_distance %})). Applicable when `manualDrag` is set to `false`. * * @default 0 */ threshold = 0; /** * Defines the automatic container scrolling behavior when close to the edge ([see example]({% slug auto_scroll %})). * * @default true */ autoScroll = true; /** * Defines a unique identifier for the dragTarget. */ dragTargetId; /** * Defines the delay in milliseconds after which the drag will begin ([see example]({% slug drag_delay %})). * * @default 0 */ dragDelay = 0; /** * Restricts the element to be dragged horizontally or vertically only ([see example]({% slug axis_lock %})). Applicable when `mode` is set to `auto`. */ restrictByAxis; /** * Specifies whether the default dragging behavior will be performed or the developer will manually handle the drag action. * * @default 'auto' */ mode = 'auto'; /** * Defines a callback function used for attaching custom data to the dragTarget. * The data will be available in the events of the respective [`DropTarget`]({% slug api_utils_droptargetdirective %}) or [`DropTargetContainer`]({% slug api_utils_droptargetcontainerdirective %}) directives. * The current DragTarget HTML element and its `dragTargetId` will be available as arguments. */ set dragData(fn) { if (isDevMode && typeof fn !== 'function') { throw new Error(`dragData must be a function, but received ${JSON.stringify(fn)}.`); } this._dragData = fn; } get dragData() { return this._dragData; } /** * Specifies the cursor style of the drag target. Accepts same values as the [CSS `cursor` property](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values). * * @default 'move' */ cursorStyle = 'move'; /** * Fires when the user presses the DragTarget element. */ onPress = new EventEmitter(); /** * Fires when the dragging of the DragTarget element begins. */ onDragStart = new EventEmitter(); /** * Fires while the user drags the DragTarget element. */ onDrag = new EventEmitter(); /** * Fires when the DragTarget's `dragDelay` has passed and the user is able to drag the element. */ onDragReady = new EventEmitter(); /** * Fires when the user releases the DragTarget element after being pressed. */ onRelease = new EventEmitter(); /** * Fires when the dragging of the DragTarget ends and the element is released. */ onDragEnd = new EventEmitter(); drag