ngx-virtual-dnd 3.2.1 → 3.3.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.3.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>;
@@ -477,7 +484,8 @@ declare class DragStateService {
477
484
  */
478
485
  updateScrollOnlyPlaceholder(placeholderId: string | null, placeholderIndex: number | null): void;
479
486
  /**
480
- * End the drag operation and reset state (normal drop).
487
+ * End the drag operation and reset state (normal drop), then deliver the drop: the target
488
+ * droppable (`activeDroppableId`) emits `drop` before this returns.
481
489
  */
482
490
  endDrag(): void;
483
491
  /**
@@ -511,6 +519,7 @@ declare class DragStateService {
511
519
  */
512
520
  declare class PositionCalculatorService {
513
521
  #private;
522
+ constructor();
514
523
  /**
515
524
  * Begin a drag session: snapshot the candidate droppables for `groupName` and
516
525
  * their rects once, then watch for viewport changes (scroll/resize) that would
@@ -532,22 +541,22 @@ declare class PositionCalculatorService {
532
541
  */
533
542
  invalidateDroppableRects(): void;
534
543
  /**
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.
544
+ * Re-read the candidate droppable *list* for the active session from the registry and
545
+ * re-read their rects. Unlike {@link invalidateDroppableRects} (which only refreshes
546
+ * rects), this picks up droppables added or removed since the snapshot was captured at
547
+ * drag start. Registration changes already trigger it on the next hit-test; this is the
548
+ * manual escape hatch.
539
549
  *
540
550
  * Safe to call when no session is active (no-op).
541
551
  */
542
552
  refreshCandidates(): void;
543
553
  /**
544
554
  * 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.
555
+ * registered or unregistered mid-drag). Defers the re-read to the next hit-test, keeping
556
+ * this notification path cheap.
548
557
  *
549
- * Called by {@link DroppableDirective} lifecycle hooks. No-op when the active session
550
- * belongs to a different group or no session is active.
558
+ * Called for every droppable registry change. No-op when the active session belongs to
559
+ * a different group or no session is active.
551
560
  */
552
561
  notifyCandidatesChanged(groupName: string): void;
553
562
  /**
@@ -569,8 +578,8 @@ declare class PositionCalculatorService {
569
578
  /**
570
579
  * Look up a droppable element by its ID.
571
580
  *
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.
581
+ * When a drag session is active, searches the cached candidate list. Otherwise looks the
582
+ * ID up in the droppable registry. Neither queries the document.
574
583
  *
575
584
  * Intended for the autoscroll scroll-only fast path, where the active droppable is already
576
585
  * known and only the placeholder index needs recalculation.
@@ -892,8 +901,14 @@ interface VirtualScrollItemContext<T> {
892
901
  * </vdnd-virtual-scroll>
893
902
  * ```
894
903
  */
895
- declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewInit, OnDestroy {
904
+ declare class VirtualScrollContainerComponent<T> implements OnChanges, OnInit, AfterViewInit, AfterViewChecked, OnDestroy {
896
905
  #private;
906
+ /** The template's spacer (cannot use ES private with viewChild) */
907
+ private readonly spacer;
908
+ /** The template's content wrapper (cannot use ES private with viewChild) */
909
+ private readonly contentWrapper;
910
+ /** Where #renderRows inserts the rows (cannot use ES private with viewChild) */
911
+ private readonly rowOutlet;
897
912
  /** Template for rendering each item - passed as input instead of content child for reliability */
898
913
  itemTemplate: _angular_core.InputSignal<TemplateRef<VirtualScrollItemContext<T>>>;
899
914
  /** Unique ID for this scroll container (used for auto-scroll registration) */
@@ -940,6 +955,13 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
940
955
  * Required for placeholder positioning.
941
956
  */
942
957
  droppableId: _angular_core.InputSignal<string | undefined>;
958
+ /**
959
+ * Reuse the views of rows that scroll out to render the rows that scroll in, instead of
960
+ * destroying them and creating new ones. Scrolling creates fewer components and elements, but
961
+ * a row's components, element and DOM state (focus aside) then carry over to other items.
962
+ * @default false
963
+ */
964
+ recycleRows: _angular_core.InputSignal<boolean>;
943
965
  /**
944
966
  * Whether to automatically add the dragged item to the sticky list.
945
967
  * This ensures the dragged item remains visible during virtual scrolling.
@@ -983,8 +1005,10 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
983
1005
  isDragging: boolean;
984
1006
  }[]>;
985
1007
  constructor();
1008
+ ngOnChanges(): void;
986
1009
  ngOnInit(): void;
987
1010
  ngAfterViewInit(): void;
1011
+ ngAfterViewChecked(): void;
988
1012
  ngOnDestroy(): void;
989
1013
  /**
990
1014
  * Scroll to a specific position.
@@ -1007,7 +1031,7 @@ declare class VirtualScrollContainerComponent<T> implements OnInit, AfterViewIni
1007
1031
  */
1008
1032
  scrollBy(delta: number): void;
1009
1033
  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>;
1034
+ 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
1035
  }
1012
1036
 
1013
1037
  /**
@@ -1062,12 +1086,11 @@ declare class DragPreviewComponent<T = unknown> implements OnDestroy {
1062
1086
  protected readonly clonedElement: _angular_core.Signal<HTMLElement | null>;
1063
1087
  constructor();
1064
1088
  ngOnDestroy(): void;
1065
- /** Whether the preview is visible */
1089
+ /**
1090
+ * Whether the preview is visible. A drag shows it once it has a position: `initialPosition`
1091
+ * is set from the drag-start cursor and, unlike `cursorPosition`, does not change per frame.
1092
+ */
1066
1093
  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
1094
  /** Dimensions of the preview */
1072
1095
  protected readonly dimensions: _angular_core.Signal<{
1073
1096
  width: number;
@@ -1210,6 +1233,13 @@ declare class VirtualSortableListComponent<T> {
1210
1233
  containerHeight: _angular_core.InputSignal<number | undefined>;
1211
1234
  /** Number of items to render above/below the visible area */
1212
1235
  overscan: _angular_core.InputSignal<number>;
1236
+ /**
1237
+ * Reuse the views of rows that scroll out to render the rows that scroll in, instead of
1238
+ * destroying them and creating new ones. Scrolling creates fewer components and elements, but
1239
+ * a row's components, element and DOM state (focus aside) then carry over to other items.
1240
+ * @default false
1241
+ */
1242
+ recycleRows: _angular_core.InputSignal<boolean>;
1213
1243
  /** Enable auto-scroll when dragging near edges */
1214
1244
  autoScrollEnabled: _angular_core.InputSignal<boolean>;
1215
1245
  /** Auto-scroll configuration */
@@ -1221,7 +1251,7 @@ declare class VirtualSortableListComponent<T> {
1221
1251
  /** Emits each time the placeholder moves within this list (every item displacement) */
1222
1252
  placeholderMove: _angular_core.OutputEmitterRef<PlaceholderMoveEvent>;
1223
1253
  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>;
1254
+ 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
1255
  }
1226
1256
 
1227
1257
  /**
@@ -1407,7 +1437,7 @@ declare class VirtualContentComponent implements VdndVirtualViewport, VdndScroll
1407
1437
  * </div>
1408
1438
  * ```
1409
1439
  */
1410
- declare class DraggableDirective implements OnInit, OnDestroy {
1440
+ declare class DraggableDirective implements OnChanges, OnInit, OnDestroy {
1411
1441
  #private;
1412
1442
  /** Unique identifier for this draggable */
1413
1443
  vdndDraggable: _angular_core.InputSignal<string>;
@@ -1441,10 +1471,16 @@ declare class DraggableDirective implements OnInit, OnDestroy {
1441
1471
  dragStart: _angular_core.OutputEmitterRef<DragStartEvent>;
1442
1472
  /** Emits when drag ends */
1443
1473
  dragEnd: _angular_core.OutputEmitterRef<DragEndEvent>;
1474
+ /**
1475
+ * Whether this element is in the "ready to drag" state (delay has passed). Tied to the pressed
1476
+ * item: a recycled row that renders another item is not ready to drag.
1477
+ */
1444
1478
  readonly isPending: _angular_core.Signal<boolean>;
1445
1479
  /** Whether this element is currently being dragged (based on global drag state) */
1446
1480
  readonly isDragging: _angular_core.Signal<boolean>;
1481
+ constructor();
1447
1482
  ngOnInit(): void;
1483
+ ngOnChanges(): void;
1448
1484
  ngOnDestroy(): void;
1449
1485
  /**
1450
1486
  * Handle pointer down (mouse or touch).
@@ -1516,7 +1552,7 @@ declare class DroppableDirective implements OnDestroy {
1516
1552
  autoScrollConfig: _angular_core.InputSignal<Partial<AutoScrollConfig>>;
1517
1553
  /** Constrain drag preview and placeholder to container boundaries */
1518
1554
  constrainToContainer: _angular_core.InputSignal<boolean>;
1519
- /** Emits when an item is dropped on this droppable */
1555
+ /** Emits when an item is dropped on this droppable: at release, right after its `dragEnd` */
1520
1556
  drop: _angular_core.OutputEmitterRef<DropEvent>;
1521
1557
  /**
1522
1558
  * Emits each time the placeholder moves within this droppable during a drag