@acorex/cdk 21.0.0-next.13 → 21.0.0-next.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/drag-drop/index.d.ts
CHANGED
|
@@ -39,7 +39,6 @@ declare class AXDragDirective implements OnInit, OnDestroy {
|
|
|
39
39
|
private dragStartTime;
|
|
40
40
|
private isDragging;
|
|
41
41
|
private elementOpacity;
|
|
42
|
-
private isDelayStarted;
|
|
43
42
|
private movedAfterDelay;
|
|
44
43
|
private activePointerId;
|
|
45
44
|
private prevDropZone;
|
|
@@ -116,7 +115,7 @@ interface AXDropListDroppedEvent extends NXNativeEvent<AXDropListDirective, Mous
|
|
|
116
115
|
* This directive automatically detects `gap` from flexbox/grid layouts and handles
|
|
117
116
|
* items with variable sizes and margins.
|
|
118
117
|
*/
|
|
119
|
-
declare class AXDropListDirective extends NXComponent implements OnInit, AfterContentInit {
|
|
118
|
+
declare class AXDropListDirective extends NXComponent implements OnInit, AfterContentInit, OnDestroy {
|
|
120
119
|
private readonly _zone;
|
|
121
120
|
private readonly _renderer;
|
|
122
121
|
private readonly _cdr;
|
|
@@ -148,8 +147,11 @@ declare class AXDropListDirective extends NXComponent implements OnInit, AfterCo
|
|
|
148
147
|
private readonly _listGap;
|
|
149
148
|
/** A signal-based alias for the orientation input for internal use. */
|
|
150
149
|
private readonly _orientation;
|
|
150
|
+
/** DOM placeholder element for inter-list drags */
|
|
151
|
+
private _placeholderElement;
|
|
151
152
|
ngOnInit(): void;
|
|
152
153
|
ngAfterContentInit(): void;
|
|
154
|
+
ngOnDestroy(): void;
|
|
153
155
|
/** Checks if the given drag item is the one currently active in this list. */
|
|
154
156
|
isDragActiveForThisList(dragItem: AXDragDirective): boolean;
|
|
155
157
|
/**
|
|
@@ -190,8 +192,12 @@ declare class AXDropListDirective extends NXComponent implements OnInit, AfterCo
|
|
|
190
192
|
* @returns The calculated placeholder index.
|
|
191
193
|
*/
|
|
192
194
|
private _calculatePlaceholderIndex;
|
|
193
|
-
/** Applies
|
|
195
|
+
/** Applies visual shifts - uses placeholder for inter-list, transforms for intra-list. */
|
|
194
196
|
private _applyVisualShifts;
|
|
197
|
+
/** Creates or moves the placeholder element for inter-list drags */
|
|
198
|
+
private _updatePlaceholderElement;
|
|
199
|
+
/** Removes the placeholder element if it exists */
|
|
200
|
+
private _removePlaceholderElement;
|
|
195
201
|
/**
|
|
196
202
|
* Calculates the required transform in pixels for a single item.
|
|
197
203
|
* @param index The index of the item to transform.
|
|
@@ -65,16 +65,24 @@ class AXDropListDirective extends NXComponent {
|
|
|
65
65
|
this._listGap = signal(0, ...(ngDevMode ? [{ debugName: "_listGap" }] : []));
|
|
66
66
|
/** A signal-based alias for the orientation input for internal use. */
|
|
67
67
|
this._orientation = this.dropListOrientation;
|
|
68
|
+
/** DOM placeholder element for inter-list drags */
|
|
69
|
+
this._placeholderElement = null;
|
|
68
70
|
}
|
|
69
71
|
ngOnInit() {
|
|
70
72
|
if (isPlatformBrowser(this._platformId)) {
|
|
71
73
|
this.element.dataset['axDropList'] = 'true';
|
|
72
74
|
this.element.classList.add('ax-drop-list-sorting-transition');
|
|
75
|
+
// Store reference to this directive instance on the element for drag directive access
|
|
76
|
+
this.element['__axContext__'] = this;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
ngAfterContentInit() {
|
|
76
80
|
this._cdr.detectChanges();
|
|
77
81
|
}
|
|
82
|
+
ngOnDestroy() {
|
|
83
|
+
// Clean up placeholder element if directive destroyed during drag
|
|
84
|
+
this._removePlaceholderElement();
|
|
85
|
+
}
|
|
78
86
|
// --- Public Methods (for internal library use) ---
|
|
79
87
|
/** Checks if the given drag item is the one currently active in this list. */
|
|
80
88
|
isDragActiveForThisList(dragItem) {
|
|
@@ -104,6 +112,8 @@ class AXDropListDirective extends NXComponent {
|
|
|
104
112
|
enter(dragItem) {
|
|
105
113
|
if (!this.axDropList() || this.sortingDisabled() || this.isDragActiveForThisList(dragItem))
|
|
106
114
|
return;
|
|
115
|
+
// Clean up any existing placeholder from previous list
|
|
116
|
+
this._removePlaceholderElement();
|
|
107
117
|
this._activeDragItem.set(dragItem);
|
|
108
118
|
this.element.classList.add('ax-drop-list-sorting-active');
|
|
109
119
|
requestAnimationFrame(() => this._zone.run(() => this._cacheGeometry()));
|
|
@@ -267,38 +277,77 @@ class AXDropListDirective extends NXComponent {
|
|
|
267
277
|
}
|
|
268
278
|
return potentialPlaceholderIndex;
|
|
269
279
|
}
|
|
270
|
-
/** Applies
|
|
280
|
+
/** Applies visual shifts - uses placeholder for inter-list, transforms for intra-list. */
|
|
271
281
|
_applyVisualShifts() {
|
|
272
282
|
const activeItem = this._activeDragItem();
|
|
273
283
|
const originalIndex = this._cachedItems().findIndex((d) => d.item === activeItem);
|
|
274
284
|
const isIntraListDrag = originalIndex > -1;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
draggedItemSize = getItemSpace(this._cachedItems()[originalIndex]);
|
|
282
|
-
}
|
|
283
|
-
else if (activeItem) {
|
|
284
|
-
const rect = activeItem.elementRect();
|
|
285
|
-
if (rect) {
|
|
286
|
-
const style = window.getComputedStyle(activeItem.element());
|
|
287
|
-
draggedItemSize =
|
|
288
|
-
this._orientation() === 'vertical'
|
|
289
|
-
? rect.height + parseFloat(style.marginTop) + parseFloat(style.marginBottom)
|
|
290
|
-
: rect.width + parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
|
291
|
-
}
|
|
285
|
+
const placeholderIndex = this._placeholderIndex();
|
|
286
|
+
// Check if this is an "onto-node" drop list (no items, just a drop target)
|
|
287
|
+
const isOntoNodeList = this.element.dataset['dropType'] === 'onto-node';
|
|
288
|
+
if (!isIntraListDrag && activeItem && !isOntoNodeList && this._cachedItems().length > 0) {
|
|
289
|
+
// --- INTER-LIST DRAG: Use DOM placeholder for reorder lists with items ---
|
|
290
|
+
this._updatePlaceholderElement(activeItem, placeholderIndex);
|
|
292
291
|
}
|
|
293
|
-
|
|
294
|
-
// ---
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
292
|
+
else {
|
|
293
|
+
// --- INTRA-LIST DRAG: Use transforms (works well for same-list reordering) ---
|
|
294
|
+
this._removePlaceholderElement();
|
|
295
|
+
const getItemSpace = (itemData) => this._orientation() === 'vertical'
|
|
296
|
+
? itemData.height + itemData.margins.top + itemData.margins.bottom
|
|
297
|
+
: itemData.width + itemData.margins.left + itemData.margins.right;
|
|
298
|
+
const draggedItemSize = isIntraListDrag ? getItemSpace(this._cachedItems()[originalIndex]) : 0;
|
|
299
|
+
this._cachedItems().forEach((data, index) => {
|
|
300
|
+
if (data.item.dragDisabled()) {
|
|
301
|
+
this._renderer.removeStyle(data.element, 'transform');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const transform = this._calculateTransform(index, originalIndex, draggedItemSize, getItemSpace);
|
|
305
|
+
this._renderer.setStyle(data.element, 'transform', transform ? `${this._orientation() === 'vertical' ? 'translateY' : 'translateX'}(${transform}px)` : '');
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/** Creates or moves the placeholder element for inter-list drags */
|
|
310
|
+
_updatePlaceholderElement(activeItem, targetIndex) {
|
|
311
|
+
const rect = activeItem.elementRect();
|
|
312
|
+
if (!rect)
|
|
313
|
+
return;
|
|
314
|
+
// Create placeholder as a clone of the dragged element
|
|
315
|
+
if (!this._placeholderElement) {
|
|
316
|
+
// Clone the entire dragged element
|
|
317
|
+
const sourceElement = activeItem.element();
|
|
318
|
+
this._placeholderElement = sourceElement.cloneNode(true);
|
|
319
|
+
// Mark it as a placeholder
|
|
320
|
+
this._renderer.addClass(this._placeholderElement, 'ax-drop-placeholder');
|
|
321
|
+
this._renderer.addClass(this._placeholderElement, 'ax-drag-placeholder');
|
|
322
|
+
// Prevent interaction with cloned content
|
|
323
|
+
this._renderer.setStyle(this._placeholderElement, 'pointerEvents', 'none');
|
|
324
|
+
// Add faded styling to indicate it's a placeholder
|
|
325
|
+
this._renderer.setStyle(this._placeholderElement, 'opacity', '0.4');
|
|
326
|
+
this._renderer.setStyle(this._placeholderElement, 'transition', 'all 200ms ease');
|
|
327
|
+
}
|
|
328
|
+
// Insert/move placeholder at target index
|
|
329
|
+
const items = this._cachedItems();
|
|
330
|
+
if (targetIndex >= 0 && targetIndex < items.length) {
|
|
331
|
+
const targetItem = items[targetIndex];
|
|
332
|
+
if (targetItem && targetItem.element) {
|
|
333
|
+
this._renderer.insertBefore(this.element, this._placeholderElement, targetItem.element);
|
|
298
334
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
335
|
+
else {
|
|
336
|
+
// Fallback: append at end if target item invalid
|
|
337
|
+
this._renderer.appendChild(this.element, this._placeholderElement);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// Insert at end
|
|
342
|
+
this._renderer.appendChild(this.element, this._placeholderElement);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** Removes the placeholder element if it exists */
|
|
346
|
+
_removePlaceholderElement() {
|
|
347
|
+
if (this._placeholderElement) {
|
|
348
|
+
this._renderer.removeChild(this.element, this._placeholderElement);
|
|
349
|
+
this._placeholderElement = null;
|
|
350
|
+
}
|
|
302
351
|
}
|
|
303
352
|
/**
|
|
304
353
|
* Calculates the required transform in pixels for a single item.
|
|
@@ -364,6 +413,7 @@ class AXDropListDirective extends NXComponent {
|
|
|
364
413
|
/** Removes all `transform` styles from the items in this list. */
|
|
365
414
|
_resetAllTransforms() {
|
|
366
415
|
this._cachedItems().forEach((data) => this._renderer.setStyle(data.element, 'transform', ''));
|
|
416
|
+
this._removePlaceholderElement(); // Clean up inter-list placeholder
|
|
367
417
|
}
|
|
368
418
|
/** Resets the internal state of the directive to its initial values. */
|
|
369
419
|
resetSortState() {
|
|
@@ -385,6 +435,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.6", ngImpor
|
|
|
385
435
|
}]
|
|
386
436
|
}], propDecorators: { axDropList: [{ type: i0.Input, args: [{ isSignal: true, alias: "axDropList", required: false }] }], sortingDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortingDisabled", required: false }] }], dropListGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropListGroup", required: false }] }], dropListOrientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropListOrientation", required: false }] }], dropListDropped: [{ type: i0.Output, args: ["dropListDropped"] }], _draggableItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => AXDragDirective), { ...{ descendants: false }, isSignal: true }] }] } });
|
|
387
437
|
|
|
438
|
+
// Zone detection constants for nested drop lists (tree nodes)
|
|
439
|
+
const ZONE_TOP_THRESHOLD = 0.3; // Top 30% triggers reorder BEFORE
|
|
440
|
+
const ZONE_BOTTOM_THRESHOLD = 0.7; // Bottom 30% triggers reorder AFTER
|
|
441
|
+
// Middle 40% (between thresholds) triggers drop INTO
|
|
388
442
|
class AXDragDirective {
|
|
389
443
|
constructor() {
|
|
390
444
|
this.zone = inject(NgZone);
|
|
@@ -418,7 +472,6 @@ class AXDragDirective {
|
|
|
418
472
|
this.dragStartTime = signal(0, ...(ngDevMode ? [{ debugName: "dragStartTime" }] : []));
|
|
419
473
|
this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
420
474
|
this.elementOpacity = signal('1', ...(ngDevMode ? [{ debugName: "elementOpacity" }] : []));
|
|
421
|
-
this.isDelayStarted = signal(false, ...(ngDevMode ? [{ debugName: "isDelayStarted" }] : []));
|
|
422
475
|
this.movedAfterDelay = signal(false, ...(ngDevMode ? [{ debugName: "movedAfterDelay" }] : []));
|
|
423
476
|
this.activePointerId = signal(null, ...(ngDevMode ? [{ debugName: "activePointerId" }] : []));
|
|
424
477
|
this.prevDropZone = signal(null, ...(ngDevMode ? [{ debugName: "prevDropZone" }] : []));
|
|
@@ -552,7 +605,6 @@ class AXDragDirective {
|
|
|
552
605
|
}
|
|
553
606
|
handlePointerMove(e) {
|
|
554
607
|
if (!this.isDragging() ||
|
|
555
|
-
this.isDelayStarted() ||
|
|
556
608
|
!isPlatformBrowser(this.platformId) ||
|
|
557
609
|
e.pointerId !== this.activePointerId()) {
|
|
558
610
|
return;
|
|
@@ -796,10 +848,37 @@ class AXDragDirective {
|
|
|
796
848
|
.map((el) => el['__axContext__']);
|
|
797
849
|
}
|
|
798
850
|
getDropListFromPoint(x, y) {
|
|
799
|
-
const
|
|
851
|
+
const dropListElements = this.document
|
|
800
852
|
.elementsFromPoint(x, y)
|
|
801
|
-
.
|
|
802
|
-
|
|
853
|
+
.filter((el) => el instanceof HTMLElement && el.dataset['axDropList'] === 'true');
|
|
854
|
+
if (dropListElements.length === 0) {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
// If only one drop list, use it
|
|
858
|
+
if (dropListElements.length === 1) {
|
|
859
|
+
return dropListElements[0]['__axContext__'];
|
|
860
|
+
}
|
|
861
|
+
// Multiple drop lists detected (nested scenario like tree2)
|
|
862
|
+
// Prioritize based on pointer position and drop type
|
|
863
|
+
const ontoNodeList = dropListElements.find((el) => el.dataset['dropType'] === 'onto-node');
|
|
864
|
+
const reorderList = dropListElements.find((el) => el.dataset['dropType'] !== 'onto-node');
|
|
865
|
+
// If no special handling needed, return first
|
|
866
|
+
if (!ontoNodeList || !reorderList) {
|
|
867
|
+
return dropListElements[0]['__axContext__'];
|
|
868
|
+
}
|
|
869
|
+
// Calculate pointer position relative to the "onto-node" element
|
|
870
|
+
const ontoNodeRect = ontoNodeList.getBoundingClientRect();
|
|
871
|
+
const relativeY = y - ontoNodeRect.top;
|
|
872
|
+
const heightRatio = relativeY / ontoNodeRect.height;
|
|
873
|
+
// Smart zone detection based on pointer position
|
|
874
|
+
if (heightRatio < ZONE_TOP_THRESHOLD || heightRatio > ZONE_BOTTOM_THRESHOLD) {
|
|
875
|
+
// Top or bottom zone: reorder (drop BEFORE/AFTER node)
|
|
876
|
+
return reorderList['__axContext__'];
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
// Center zone: onto-node (drop INTO node as child)
|
|
880
|
+
return ontoNodeList['__axContext__'];
|
|
881
|
+
}
|
|
803
882
|
}
|
|
804
883
|
dropZoneValidation(dropZone) {
|
|
805
884
|
if (!dropZone)
|
|
@@ -930,8 +1009,8 @@ class AXDragDirective {
|
|
|
930
1009
|
try {
|
|
931
1010
|
target.style.setProperty(propName, computedStyles.getPropertyValue(propName), computedStyles.getPropertyPriority(propName));
|
|
932
1011
|
}
|
|
933
|
-
catch
|
|
934
|
-
|
|
1012
|
+
catch {
|
|
1013
|
+
// Some CSS properties cannot be set directly, skip silently
|
|
935
1014
|
}
|
|
936
1015
|
}
|
|
937
1016
|
}
|