ngx-virtual-dnd 3.2.1 → 3.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-virtual-dnd",
3
- "version": "3.2.1",
3
+ "version": "3.4.0",
4
4
  "description": "A performant drag-and-drop library for Angular that works seamlessly with virtual scrolling",
5
5
  "keywords": [
6
6
  "angular",
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { Signal, InjectionToken, OnDestroy, OnInit, AfterViewInit, TemplateRef, ElementRef, WritableSignal } from '@angular/core';
2
+ import { Signal, InjectionToken, OnDestroy, OnChanges, OnInit, AfterViewInit, AfterViewChecked, TemplateRef, ElementRef, WritableSignal } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * Represents the item currently being dragged.
@@ -116,7 +116,8 @@ interface DropDestination {
116
116
  data?: unknown;
117
117
  }
118
118
  /**
119
- * Event emitted when an item is dropped.
119
+ * Event emitted by the destination droppable when an item is dropped on it: at release,
120
+ * right after the dragged item's `DragEndEvent` and before the next render.
120
121
  */
121
122
  interface DropEvent {
122
123
  /** Information about where the item came from */
@@ -173,7 +174,7 @@ interface DragEndEvent {
173
174
  /**
174
175
  * Final 0-indexed insertion position, or `null` when there is no valid drop target —
175
176
  * a cancelled drag, a release outside every droppable, or a release over a disabled
176
- * droppable. A non-null value pairs with a `DropEvent` on the destination.
177
+ * droppable. A non-null value pairs with the `DropEvent` the destination emits right after.
177
178
  */
178
179
  destinationIndex: number | null;
179
180
  }
@@ -243,6 +244,12 @@ declare class FixedHeightStrategy implements VirtualScrollStrategy {
243
244
  getItemHeight(index: number): number;
244
245
  setMeasuredHeight(key: unknown, height: number): void;
245
246
  setItemKeys(keys: unknown[]): void;
247
+ /**
248
+ * Set the item count: all this strategy needs of the keys, so the library's containers skip
249
+ * computing them.
250
+ * @internal
251
+ */
252
+ setItemCount(count: number): void;
246
253
  setExcludedIndex(index: number | null): void;
247
254
  findIndexAtOffset(offset: number): number;
248
255
  getItemCount(): number;
@@ -405,7 +412,7 @@ declare const VDND_ANIMATION_CONFIG: InjectionToken<VdndAnimationConfig>;
405
412
  */
406
413
  declare class DragStateService {
407
414
  #private;
408
- /** Whether the last drag was cancelled (for droppable to check before emitting drop) */
415
+ /** Whether the last drag was cancelled (a cancelled drag delivers no drop) */
409
416
  readonly wasCancelled: _angular_core.Signal<boolean>;
410
417
  /** Snapshot captured synchronously before the last drag reset. */
411
418
  readonly endedDragState: _angular_core.Signal<DragState | null>;
@@ -445,8 +452,9 @@ declare class DragStateService {
445
452
  /**
446
453
  * Start a drag operation.
447
454
  * `placeholderId` is deprecated with the `placeholderId` signal; the library passes `END_OF_LIST`.
455
+ * `isTouchDrag` marks a drag started by touch, which gets no grabbing cursor.
448
456
  */
449
- startDrag(item: DraggedItem, cursorPosition?: CursorPosition, grabOffset?: GrabOffset, lockAxis?: 'x' | 'y' | null, activeDroppableId?: string | null, placeholderId?: string | null, placeholderIndex?: number | null, sourceIndex?: number | null, isKeyboardDrag?: boolean, axisLockPosition?: CursorPosition): void;
457
+ startDrag(item: DraggedItem, cursorPosition?: CursorPosition, grabOffset?: GrabOffset, lockAxis?: 'x' | 'y' | null, activeDroppableId?: string | null, placeholderId?: string | null, placeholderIndex?: number | null, sourceIndex?: number | null, isKeyboardDrag?: boolean, axisLockPosition?: CursorPosition, isTouchDrag?: boolean): void;
450
458
  /**
451
459
  * Update the drag position and targets.
452
460
  * `placeholderId` is deprecated with the `placeholderId` signal; the library passes `END_OF_LIST`.
@@ -477,7 +485,8 @@ declare class DragStateService {
477
485
  */
478
486
  updateScrollOnlyPlaceholder(placeholderId: string | null, placeholderIndex: number | null): void;
479
487
  /**
480
- * End the drag operation and reset state (normal drop).
488
+ * End the drag operation and reset state (normal drop), then deliver the drop: the target
489
+ * droppable (`activeDroppableId`) emits `drop` before this returns.
481
490
  */
482
491
  endDrag(): void;
483
492
  /**
@@ -511,6 +520,7 @@ declare class DragStateService {
511
520
  */
512
521
  declare class PositionCalculatorService {
513
522
  #private;
523
+ constructor();
514
524
  /**
515
525
  * Begin a drag session: snapshot the candidate droppables for `groupName` and
516
526
  * their rects once, then watch for viewport changes (scroll/resize) that would
@@ -532,22 +542,22 @@ declare class PositionCalculatorService {
532
542
  */
533
543
  invalidateDroppableRects(): void;
534
544
  /**
535
- * Re-query the candidate droppable *list* for the active session and re-read their
536
- * rects. Unlike {@link invalidateDroppableRects} (which only refreshes rects), this
537
- * picks up droppables added or removed since the snapshot was captured at drag start —
538
- * the frozen candidate list is otherwise blind to mid-drag mounts/unmounts.
545
+ * Re-read the candidate droppable *list* for the active session from the registry and
546
+ * re-read their rects. Unlike {@link invalidateDroppableRects} (which only refreshes
547
+ * rects), this picks up droppables added or removed since the snapshot was captured at
548
+ * drag start. Registration changes already trigger it on the next hit-test; this is the
549
+ * manual escape hatch.
539
550
  *
540
551
  * Safe to call when no session is active (no-op).
541
552
  */
542
553
  refreshCandidates(): void;
543
554
  /**
544
555
  * Signal that the set of droppables for `groupName` may have changed (a droppable
545
- * registered or unregistered mid-drag). Defers the actual re-query to the next
546
- * hit-test — where a removed element is already gone from the DOM and a newly added
547
- * one has its data attributes applied — keeping this notification path cheap.
556
+ * registered or unregistered mid-drag). Defers the re-read to the next hit-test, keeping
557
+ * this notification path cheap.
548
558
  *
549
- * Called by {@link DroppableDirective} lifecycle hooks. No-op when the active session
550
- * belongs to a different group or no session is active.
559
+ * Called for every droppable registry change. No-op when the active session belongs to
560
+ * a different group or no session is active.
551
561
  */
552
562
  notifyCandidatesChanged(groupName: string): void;
553
563
  /**
@@ -569,8 +579,8 @@ declare class PositionCalculatorService {
569
579
  /**
570
580
  * Look up a droppable element by its ID.
571
581
  *
572
- * When a drag session is active, searches the cached candidate list (O(n), avoids a DOM
573
- * query). Falls back to `document.querySelector` when no session is active.
582
+ * When a drag session is active, searches the cached candidate list. Otherwise looks the
583
+ * ID up in the droppable registry. Neither queries the document.
574
584
  *
575
585
  * Intended for the autoscroll scroll-only fast path, where the active droppable is already
576
586
  * known and only the placeholder index needs recalculation.
@@ -892,8 +902,14 @@ interface VirtualScrollItemContext<T> {
892
902
  * </vdnd-virtual-scroll>
893
903
  * ```
894
904
  */
895
- declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewInit, OnDestroy {
905
+ declare class VirtualScrollContainerComponent<T> implements OnChanges, OnInit, AfterViewInit, AfterViewChecked, OnDestroy {
896
906
  #private;
907
+ /** The template's spacer (cannot use ES private with viewChild) */
908
+ private readonly spacer;
909
+ /** The template's content wrapper (cannot use ES private with viewChild) */
910
+ private readonly contentWrapper;
911
+ /** Where #renderRows inserts the rows (cannot use ES private with viewChild) */
912
+ private readonly rowOutlet;
897
913
  /** Template for rendering each item - passed as input instead of content child for reliability */
898
914
  itemTemplate: _angular_core.InputSignal<TemplateRef<VirtualScrollItemContext<T>>>;
899
915
  /** Unique ID for this scroll container (used for auto-scroll registration) */
@@ -940,6 +956,13 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
940
956
  * Required for placeholder positioning.
941
957
  */
942
958
  droppableId: _angular_core.InputSignal<string | undefined>;
959
+ /**
960
+ * Reuse the views of rows that scroll out to render the rows that scroll in, instead of
961
+ * destroying them and creating new ones. Scrolling creates fewer components and elements, but
962
+ * a row's components, element and DOM state (focus aside) then carry over to other items.
963
+ * @default false
964
+ */
965
+ recycleRows: _angular_core.InputSignal<boolean>;
943
966
  /**
944
967
  * Whether to automatically add the dragged item to the sticky list.
945
968
  * This ensures the dragged item remains visible during virtual scrolling.
@@ -983,8 +1006,10 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
983
1006
  isDragging: boolean;
984
1007
  }[]>;
985
1008
  constructor();
1009
+ ngOnChanges(): void;
986
1010
  ngOnInit(): void;
987
1011
  ngAfterViewInit(): void;
1012
+ ngAfterViewChecked(): void;
988
1013
  ngOnDestroy(): void;
989
1014
  /**
990
1015
  * Scroll to a specific position.
@@ -1007,7 +1032,7 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
1007
1032
  */
1008
1033
  scrollBy(delta: number): void;
1009
1034
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<VirtualScrollContainerComponent<any>, never>;
1010
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<VirtualScrollContainerComponent<any>, "vdnd-virtual-scroll", never, { "itemTemplate": { "alias": "itemTemplate"; "required": true; "isSignal": true; }; "scrollContainerId": { "alias": "scrollContainerId"; "required": false; "isSignal": true; }; "autoScrollEnabled": { "alias": "autoScrollEnabled"; "required": false; "isSignal": true; }; "autoScrollConfig": { "alias": "autoScrollConfig"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "dynamicItemHeight": { "alias": "dynamicItemHeight"; "required": false; "isSignal": true; }; "containerHeight": { "alias": "containerHeight"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "stickyItemIds": { "alias": "stickyItemIds"; "required": false; "isSignal": true; }; "itemIdFn": { "alias": "itemIdFn"; "required": true; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "droppableId": { "alias": "droppableId"; "required": false; "isSignal": true; }; "autoStickyDraggedItem": { "alias": "autoStickyDraggedItem"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1035
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<VirtualScrollContainerComponent<any>, "vdnd-virtual-scroll", never, { "itemTemplate": { "alias": "itemTemplate"; "required": true; "isSignal": true; }; "scrollContainerId": { "alias": "scrollContainerId"; "required": false; "isSignal": true; }; "autoScrollEnabled": { "alias": "autoScrollEnabled"; "required": false; "isSignal": true; }; "autoScrollConfig": { "alias": "autoScrollConfig"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "dynamicItemHeight": { "alias": "dynamicItemHeight"; "required": false; "isSignal": true; }; "containerHeight": { "alias": "containerHeight"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "stickyItemIds": { "alias": "stickyItemIds"; "required": false; "isSignal": true; }; "itemIdFn": { "alias": "itemIdFn"; "required": true; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "droppableId": { "alias": "droppableId"; "required": false; "isSignal": true; }; "recycleRows": { "alias": "recycleRows"; "required": false; "isSignal": true; }; "autoStickyDraggedItem": { "alias": "autoStickyDraggedItem"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1011
1036
  }
1012
1037
 
1013
1038
  /**
@@ -1062,12 +1087,11 @@ declare class DragPreviewComponent<T = unknown> implements OnDestroy {
1062
1087
  protected readonly clonedElement: _angular_core.Signal<HTMLElement | null>;
1063
1088
  constructor();
1064
1089
  ngOnDestroy(): void;
1065
- /** Whether the preview is visible */
1090
+ /**
1091
+ * Whether the preview is visible. A drag shows it once it has a position: `initialPosition`
1092
+ * is set from the drag-start cursor and, unlike `cursorPosition`, does not change per frame.
1093
+ */
1066
1094
  protected readonly isVisible: _angular_core.Signal<boolean>;
1067
- /** Position of the preview */
1068
- protected readonly position: _angular_core.Signal<CursorPosition>;
1069
- /** Transform-based positioning for better performance (avoid layout from left/top). */
1070
- protected readonly transform: _angular_core.Signal<string>;
1071
1095
  /** Dimensions of the preview */
1072
1096
  protected readonly dimensions: _angular_core.Signal<{
1073
1097
  width: number;
@@ -1210,6 +1234,13 @@ declare class VirtualSortableListComponent<T> {
1210
1234
  containerHeight: _angular_core.InputSignal<number | undefined>;
1211
1235
  /** Number of items to render above/below the visible area */
1212
1236
  overscan: _angular_core.InputSignal<number>;
1237
+ /**
1238
+ * Reuse the views of rows that scroll out to render the rows that scroll in, instead of
1239
+ * destroying them and creating new ones. Scrolling creates fewer components and elements, but
1240
+ * a row's components, element and DOM state (focus aside) then carry over to other items.
1241
+ * @default false
1242
+ */
1243
+ recycleRows: _angular_core.InputSignal<boolean>;
1213
1244
  /** Enable auto-scroll when dragging near edges */
1214
1245
  autoScrollEnabled: _angular_core.InputSignal<boolean>;
1215
1246
  /** Auto-scroll configuration */
@@ -1221,7 +1252,7 @@ declare class VirtualSortableListComponent<T> {
1221
1252
  /** Emits each time the placeholder moves within this list (every item displacement) */
1222
1253
  placeholderMove: _angular_core.OutputEmitterRef<PlaceholderMoveEvent>;
1223
1254
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<VirtualSortableListComponent<any>, never>;
1224
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<VirtualSortableListComponent<any>, "vdnd-sortable-list", never, { "droppableId": { "alias": "droppableId"; "required": true; "isSignal": true; }; "group": { "alias": "group"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "dynamicItemHeight": { "alias": "dynamicItemHeight"; "required": false; "isSignal": true; }; "itemIdFn": { "alias": "itemIdFn"; "required": true; "isSignal": true; }; "itemTemplate": { "alias": "itemTemplate"; "required": true; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "droppableData": { "alias": "droppableData"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "containerHeight": { "alias": "containerHeight"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "autoScrollEnabled": { "alias": "autoScrollEnabled"; "required": false; "isSignal": true; }; "autoScrollConfig": { "alias": "autoScrollConfig"; "required": false; "isSignal": true; }; "constrainToContainer": { "alias": "constrainToContainer"; "required": false; "isSignal": true; }; }, { "drop": "drop"; "placeholderMove": "placeholderMove"; }, never, never, true, never>;
1255
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<VirtualSortableListComponent<any>, "vdnd-sortable-list", never, { "droppableId": { "alias": "droppableId"; "required": true; "isSignal": true; }; "group": { "alias": "group"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": true; "isSignal": true; }; "dynamicItemHeight": { "alias": "dynamicItemHeight"; "required": false; "isSignal": true; }; "itemIdFn": { "alias": "itemIdFn"; "required": true; "isSignal": true; }; "itemTemplate": { "alias": "itemTemplate"; "required": true; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; "droppableData": { "alias": "droppableData"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "containerHeight": { "alias": "containerHeight"; "required": false; "isSignal": true; }; "overscan": { "alias": "overscan"; "required": false; "isSignal": true; }; "recycleRows": { "alias": "recycleRows"; "required": false; "isSignal": true; }; "autoScrollEnabled": { "alias": "autoScrollEnabled"; "required": false; "isSignal": true; }; "autoScrollConfig": { "alias": "autoScrollConfig"; "required": false; "isSignal": true; }; "constrainToContainer": { "alias": "constrainToContainer"; "required": false; "isSignal": true; }; }, { "drop": "drop"; "placeholderMove": "placeholderMove"; }, never, never, true, never>;
1225
1256
  }
1226
1257
 
1227
1258
  /**
@@ -1407,7 +1438,7 @@ declare class VirtualContentComponent implements VdndVirtualViewport, VdndScroll
1407
1438
  * </div>
1408
1439
  * ```
1409
1440
  */
1410
- declare class DraggableDirective implements OnInit, OnDestroy {
1441
+ declare class DraggableDirective implements OnChanges, OnInit, OnDestroy {
1411
1442
  #private;
1412
1443
  /** Unique identifier for this draggable */
1413
1444
  vdndDraggable: _angular_core.InputSignal<string>;
@@ -1441,10 +1472,16 @@ declare class DraggableDirective implements OnInit, OnDestroy {
1441
1472
  dragStart: _angular_core.OutputEmitterRef<DragStartEvent>;
1442
1473
  /** Emits when drag ends */
1443
1474
  dragEnd: _angular_core.OutputEmitterRef<DragEndEvent>;
1475
+ /**
1476
+ * Whether this element is in the "ready to drag" state (delay has passed). Tied to the pressed
1477
+ * item: a recycled row that renders another item is not ready to drag.
1478
+ */
1444
1479
  readonly isPending: _angular_core.Signal<boolean>;
1445
1480
  /** Whether this element is currently being dragged (based on global drag state) */
1446
1481
  readonly isDragging: _angular_core.Signal<boolean>;
1482
+ constructor();
1447
1483
  ngOnInit(): void;
1484
+ ngOnChanges(): void;
1448
1485
  ngOnDestroy(): void;
1449
1486
  /**
1450
1487
  * Handle pointer down (mouse or touch).
@@ -1516,7 +1553,7 @@ declare class DroppableDirective implements OnDestroy {
1516
1553
  autoScrollConfig: _angular_core.InputSignal<Partial<AutoScrollConfig>>;
1517
1554
  /** Constrain drag preview and placeholder to container boundaries */
1518
1555
  constrainToContainer: _angular_core.InputSignal<boolean>;
1519
- /** Emits when an item is dropped on this droppable */
1556
+ /** Emits when an item is dropped on this droppable: at release, right after its `dragEnd` */
1520
1557
  drop: _angular_core.OutputEmitterRef<DropEvent>;
1521
1558
  /**
1522
1559
  * Emits each time the placeholder moves within this droppable during a drag