ngx-virtual-dnd 3.1.2 → 3.2.0-alpha.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.1.2",
3
+ "version": "3.2.0-alpha.0",
4
4
  "description": "A performant drag-and-drop library for Angular that works seamlessly with virtual scrolling",
5
5
  "keywords": [
6
6
  "angular",
@@ -19,11 +19,11 @@
19
19
  "license": "MIT",
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "git+https://github.com/gultyayev/angular-vdnd.git"
22
+ "url": "git+https://github.com/gultyayev/ngx-virtual-dnd.git"
23
23
  },
24
- "homepage": "https://github.com/gultyayev/angular-vdnd#readme",
24
+ "homepage": "https://github.com/gultyayev/ngx-virtual-dnd#readme",
25
25
  "bugs": {
26
- "url": "https://github.com/gultyayev/angular-vdnd/issues"
26
+ "url": "https://github.com/gultyayev/ngx-virtual-dnd/issues"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@angular/common": "^21.0.0 || ^22.0.0",
@@ -59,7 +59,7 @@ interface DragState {
59
59
  grabOffset: GrabOffset | null;
60
60
  /** Position when drag started (for axis locking) */
61
61
  initialPosition: CursorPosition | null;
62
- /** Axis to lock movement to ('x' = horizontal only, 'y' = vertical only) */
62
+ /** Axis to freeze ('x' = X frozen → vertical-only, 'y' = Y frozen → horizontal-only) */
63
63
  lockAxis: 'x' | 'y' | null;
64
64
  /** Whether this is a keyboard-initiated drag */
65
65
  isKeyboardDrag: boolean;
@@ -116,6 +116,31 @@ interface DropEvent {
116
116
  /** Information about where the item is going */
117
117
  destination: DropDestination;
118
118
  }
119
+ /**
120
+ * Event emitted by a droppable each time the placeholder moves within it during a drag,
121
+ * i.e. whenever items are displaced. Useful for haptic feedback on every step.
122
+ *
123
+ * Indexes use the same convention as `DropEvent.destination.index` (the insertion index
124
+ * the item would get if dropped now). Not emitted for the placeholder's initial position
125
+ * at drag start, nor when the placeholder leaves this droppable.
126
+ */
127
+ interface PlaceholderMoveEvent {
128
+ /** Unique identifier for the dragged item */
129
+ draggableId: string;
130
+ /** ID of the droppable container the item originated from */
131
+ sourceDroppableId: string;
132
+ /** ID of the droppable the placeholder moved within */
133
+ droppableId: string;
134
+ /**
135
+ * Previous insertion index in this droppable, or `null` when the placeholder just entered
136
+ * it from elsewhere. For the first move in the source list this is the item's own index.
137
+ */
138
+ previousIndex: number | null;
139
+ /** New insertion index in this droppable */
140
+ currentIndex: number;
141
+ /** Optional user-provided data associated with the dragged item */
142
+ data?: unknown;
143
+ }
119
144
  /**
120
145
  * Event emitted when a drag operation ends (including cancel).
121
146
  */
@@ -124,13 +149,22 @@ interface DragEndEvent {
124
149
  draggableId: string;
125
150
  /** ID of the droppable container the item originated from */
126
151
  droppableId: string;
127
- /** Whether the drag was cancelled (escaped, dropped outside) */
152
+ /**
153
+ * Whether the drag was actively cancelled (Escape key). Releasing over no valid
154
+ * target (outside any droppable, or over a disabled droppable) is NOT a cancel —
155
+ * it reports `cancelled: false` with `destinationIndex: null`. Branch on
156
+ * `destinationIndex === null` to detect "no drop occurred".
157
+ */
128
158
  cancelled: boolean;
129
159
  /** Optional user-provided data associated with the item */
130
160
  data?: unknown;
131
161
  /** Original 0-indexed position in source list (for cancel announcements) */
132
162
  sourceIndex: number;
133
- /** Final 0-indexed position (null if cancelled) */
163
+ /**
164
+ * Final 0-indexed insertion position, or `null` when there is no valid drop target —
165
+ * a cancelled drag, a release outside every droppable, or a release over a disabled
166
+ * droppable. A non-null value pairs with a `DropEvent` on the destination.
167
+ */
134
168
  destinationIndex: number | null;
135
169
  }
136
170
  /**
@@ -302,6 +336,37 @@ interface VdndVirtualViewport {
302
336
  */
303
337
  declare const VDND_VIRTUAL_VIEWPORT: InjectionToken<VdndVirtualViewport>;
304
338
 
339
+ /**
340
+ * Animation settings for drag-and-drop lists.
341
+ */
342
+ interface VdndAnimationConfig {
343
+ /**
344
+ * Duration (ms) of the slide animation played when items are displaced by the
345
+ * placeholder during a drag. `0` disables the animation.
346
+ * @default 200
347
+ */
348
+ shiftDuration?: number;
349
+ /**
350
+ * CSS easing function for the shift animation.
351
+ * @default 'cubic-bezier(0.2, 0, 0, 1)'
352
+ */
353
+ shiftEasing?: string;
354
+ }
355
+ /**
356
+ * Opt-in animation configuration. When provided, items displaced by the placeholder
357
+ * slide into their new position instead of jumping. Resolved through the element
358
+ * injector, so it can be provided app-wide or per component subtree.
359
+ *
360
+ * The animation is skipped when the user prefers reduced motion. Values are read each
361
+ * time an animation starts, so getters can toggle the animation at runtime.
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * providers: [{ provide: VDND_ANIMATION_CONFIG, useValue: { shiftDuration: 200 } }]
366
+ * ```
367
+ */
368
+ declare const VDND_ANIMATION_CONFIG: InjectionToken<VdndAnimationConfig>;
369
+
305
370
  /**
306
371
  * Central service for managing drag-and-drop state.
307
372
  * Uses signals for reactive state management.
@@ -317,6 +382,8 @@ declare class DragStateService {
317
382
  #private;
318
383
  /** Whether the last drag was cancelled (for droppable to check before emitting drop) */
319
384
  readonly wasCancelled: _angular_core.Signal<boolean>;
385
+ /** Snapshot captured synchronously before the last drag reset. */
386
+ readonly endedDragState: _angular_core.Signal<DragState | null>;
320
387
  /** Whether a drag operation is in progress */
321
388
  readonly isDragging: _angular_core.Signal<boolean>;
322
389
  /** The currently dragged item, or null */
@@ -430,6 +497,25 @@ declare class PositionCalculatorService {
430
497
  * scroll event has not yet fired but the container has already moved.
431
498
  */
432
499
  invalidateDroppableRects(): void;
500
+ /**
501
+ * Re-query the candidate droppable *list* for the active session and re-read their
502
+ * rects. Unlike {@link invalidateDroppableRects} (which only refreshes rects), this
503
+ * picks up droppables added or removed since the snapshot was captured at drag start —
504
+ * the frozen candidate list is otherwise blind to mid-drag mounts/unmounts.
505
+ *
506
+ * Safe to call when no session is active (no-op).
507
+ */
508
+ refreshCandidates(): void;
509
+ /**
510
+ * Signal that the set of droppables for `groupName` may have changed (a droppable
511
+ * registered or unregistered mid-drag). Defers the actual re-query to the next
512
+ * hit-test — where a removed element is already gone from the DOM and a newly added
513
+ * one has its data attributes applied — keeping this notification path cheap.
514
+ *
515
+ * Called by {@link DroppableDirective} lifecycle hooks. No-op when the active session
516
+ * belongs to a different group or no session is active.
517
+ */
518
+ notifyCandidatesChanged(groupName: string): void;
433
519
  /**
434
520
  * Find the droppable element at a given point.
435
521
  *
@@ -538,8 +624,14 @@ declare class PositionCalculatorService {
538
624
  findAdjacentDroppable(currentDroppableId: string, direction: 'left' | 'right', groupName: string): {
539
625
  element: HTMLElement;
540
626
  id: string;
541
- itemCount: number;
542
627
  } | null;
628
+ /**
629
+ * Whether the droppable with the given ID is currently disabled. A droppable that cannot
630
+ * be found is reported as disabled (treat missing as not a valid target). Used to
631
+ * revalidate a keyboard drag's active target at drop time, since it may have been disabled
632
+ * after the drag navigated into it.
633
+ */
634
+ isDroppableDisabledById(id: string): boolean;
543
635
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PositionCalculatorService, never>;
544
636
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<PositionCalculatorService>;
545
637
  }
@@ -550,7 +642,7 @@ declare class PositionCalculatorService {
550
642
  interface AutoScrollConfig {
551
643
  /** Distance from edge to start scrolling (in pixels) */
552
644
  threshold: number;
553
- /** Maximum scroll speed (in pixels per frame) */
645
+ /** Maximum scroll speed in pixels per 60fps frame (about 16.67ms) */
554
646
  maxSpeed: number;
555
647
  /** Whether to accelerate scroll based on distance from edge */
556
648
  accelerate: boolean;
@@ -1075,8 +1167,10 @@ declare class VirtualSortableListComponent<T> {
1075
1167
  constrainToContainer: _angular_core.InputSignal<boolean>;
1076
1168
  /** Emits when an item is dropped on this list */
1077
1169
  drop: _angular_core.OutputEmitterRef<DropEvent>;
1170
+ /** Emits each time the placeholder moves within this list (every item displacement) */
1171
+ placeholderMove: _angular_core.OutputEmitterRef<PlaceholderMoveEvent>;
1078
1172
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<VirtualSortableListComponent<any>, never>;
1079
- 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"; }, never, never, true, never>;
1173
+ 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>;
1080
1174
  }
1081
1175
 
1082
1176
  /**
@@ -1285,7 +1379,12 @@ declare class DraggableDirective implements OnInit, OnDestroy {
1285
1379
  * Set to 0 for immediate drag (default behavior).
1286
1380
  */
1287
1381
  dragDelay: _angular_core.InputSignal<number>;
1288
- /** Lock dragging to a single axis ('x' = horizontal only, 'y' = vertical only) */
1382
+ /**
1383
+ * Lock dragging by freezing one axis. The value names the axis that stays
1384
+ * fixed: `'x'` freezes the X coordinate (vertical-only movement), `'y'`
1385
+ * freezes the Y coordinate (horizontal-only movement). `null` allows free
1386
+ * movement. Note: this is the opposite of Angular CDK's `cdkDragLockAxis`.
1387
+ */
1289
1388
  lockAxis: _angular_core.InputSignal<"x" | "y" | null>;
1290
1389
  /** Emits when drag starts */
1291
1390
  dragStart: _angular_core.OutputEmitterRef<DragStartEvent>;
@@ -1368,6 +1467,11 @@ declare class DroppableDirective implements OnDestroy {
1368
1467
  constrainToContainer: _angular_core.InputSignal<boolean>;
1369
1468
  /** Emits when an item is dropped on this droppable */
1370
1469
  drop: _angular_core.OutputEmitterRef<DropEvent>;
1470
+ /**
1471
+ * Emits each time the placeholder moves within this droppable during a drag
1472
+ * (every item displacement) — e.g. to trigger haptic feedback.
1473
+ */
1474
+ placeholderMove: _angular_core.OutputEmitterRef<PlaceholderMoveEvent>;
1371
1475
  /** Whether this droppable is currently being targeted */
1372
1476
  readonly isActive: _angular_core.Signal<boolean>;
1373
1477
  /** The current placeholder ID when this droppable is active */
@@ -1391,7 +1495,7 @@ declare class DroppableDirective implements OnDestroy {
1391
1495
  */
1392
1496
  getScrollHeight(): number;
1393
1497
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DroppableDirective, never>;
1394
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<DroppableDirective, "[vdndDroppable]", never, { "vdndDroppable": { "alias": "vdndDroppable"; "required": true; "isSignal": true; }; "vdndDroppableGroup": { "alias": "vdndDroppableGroup"; "required": false; "isSignal": true; }; "vdndDroppableData": { "alias": "vdndDroppableData"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "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"; }, never, never, true, never>;
1498
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<DroppableDirective, "[vdndDroppable]", never, { "vdndDroppable": { "alias": "vdndDroppable"; "required": true; "isSignal": true; }; "vdndDroppableGroup": { "alias": "vdndDroppableGroup"; "required": false; "isSignal": true; }; "vdndDroppableData": { "alias": "vdndDroppableData"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "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>;
1395
1499
  }
1396
1500
 
1397
1501
  /**
@@ -1700,5 +1804,5 @@ declare function insertAt<T>(list: T[], item: T, index: number): T[];
1700
1804
  */
1701
1805
  declare function removeAt<T>(list: T[], index: number): T[];
1702
1806
 
1703
- export { AutoScrollService, ContentHeaderDirective, DragPlaceholderComponent, DragPreviewComponent, DragStateService, DraggableDirective, DroppableDirective, DroppableGroupDirective, DynamicHeightStrategy, END_OF_LIST, ElementCloneService, FixedHeightStrategy, INITIAL_DRAG_STATE, KeyboardDragService, OverlayContainerService, PlaceholderComponent, PositionCalculatorService, ScrollableDirective, VDND_GROUP_TOKEN, VDND_SCROLL_CONTAINER, VDND_VIRTUAL_VIEWPORT, VirtualContentComponent, VirtualForDirective, VirtualScrollContainerComponent, VirtualSortableListComponent, VirtualViewportComponent, applyMove, insertAt, isNoOpDrop, moveItem, removeAt, reorderItems };
1704
- export type { AutoScrollConfig, CursorPosition, DragEndEvent, DragPreviewContext, DragStartEvent, DragState, DraggedItem, DropDestination, DropEvent, DropSource, GrabOffset, PlaceholderContext, VdndGroupContext, VdndScrollContainer, VdndVirtualViewport, VirtualForContext, VirtualScrollItemContext, VirtualScrollStrategy };
1807
+ export { AutoScrollService, ContentHeaderDirective, DragPlaceholderComponent, DragPreviewComponent, DragStateService, DraggableDirective, DroppableDirective, DroppableGroupDirective, DynamicHeightStrategy, END_OF_LIST, ElementCloneService, FixedHeightStrategy, INITIAL_DRAG_STATE, KeyboardDragService, OverlayContainerService, PlaceholderComponent, PositionCalculatorService, ScrollableDirective, VDND_ANIMATION_CONFIG, VDND_GROUP_TOKEN, VDND_SCROLL_CONTAINER, VDND_VIRTUAL_VIEWPORT, VirtualContentComponent, VirtualForDirective, VirtualScrollContainerComponent, VirtualSortableListComponent, VirtualViewportComponent, applyMove, insertAt, isNoOpDrop, moveItem, removeAt, reorderItems };
1808
+ export type { AutoScrollConfig, CursorPosition, DragEndEvent, DragPreviewContext, DragStartEvent, DragState, DraggedItem, DropDestination, DropEvent, DropSource, GrabOffset, PlaceholderContext, PlaceholderMoveEvent, VdndAnimationConfig, VdndGroupContext, VdndScrollContainer, VdndVirtualViewport, VirtualForContext, VirtualScrollItemContext, VirtualScrollStrategy };