ngx-virtual-dnd 3.1.1 → 3.1.3

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/README.md CHANGED
@@ -241,10 +241,13 @@ The preview cannot leave the droppable area, and the placeholder snaps to the ed
241
241
  Lock dragging to a single axis:
242
242
 
243
243
  ```html
244
+ <!-- Lock the Y axis → item moves horizontally only -->
244
245
  <div [vdndDraggable]="item.id" lockAxis="y">{{ item.name }}</div>
245
246
  ```
246
247
 
247
- Values: `'x'` (horizontal only), `'y'` (vertical only), or omit for free movement.
248
+ Values: `'x'` (X axis locked → **vertical-only** movement), `'y'` (Y axis locked → **horizontal-only** movement), or omit for free movement. The value names the axis that is _frozen_.
249
+
250
+ > **Note:** This is the opposite of Angular CDK's `cdkDragLockAxis`, where `'x'` means _movement is constrained to_ the X axis (horizontal only). Here `'x'` freezes the X axis. Keep this in mind when migrating from CDK.
248
251
 
249
252
  ### Drag Threshold & Delay
250
253
 
@@ -289,11 +292,11 @@ Configure auto-scroll behavior when dragging near container edges:
289
292
  />
290
293
  ```
291
294
 
292
- | Option | Default | Description |
293
- | ------------ | ------- | ------------------------------------ |
294
- | `threshold` | `50` | Distance from edge (px) to trigger |
295
- | `maxSpeed` | `15` | Maximum scroll speed (px/frame) |
296
- | `accelerate` | `true` | Speed up based on distance from edge |
295
+ | Option | Default | Description |
296
+ | ------------ | ------- | ---------------------------------------------- |
297
+ | `threshold` | `50` | Distance from edge (px) to trigger |
298
+ | `maxSpeed` | `15` | Maximum scroll speed in pixels per 60fps frame |
299
+ | `accelerate` | `true` | Speed up based on distance from edge |
297
300
 
298
301
  Set `[autoScrollEnabled]="false"` to disable auto-scroll entirely. These options are available on `VirtualSortableListComponent`, `DroppableDirective`, and `ScrollableDirective`.
299
302
 
@@ -320,6 +323,8 @@ Use the `disabled` input to conditionally disable draggables, droppables, or ent
320
323
 
321
324
  Disabled draggables get the `vdnd-draggable-disabled` CSS class. Disabled droppables get `vdnd-droppable-disabled`.
322
325
 
326
+ A disabled droppable is removed from all drag-time candidate sets: pointer hit-testing skips it (the cursor falls through to whatever enabled droppable sits underneath, or none) and keyboard `ArrowLeft`/`ArrowRight` navigation steps over it. Releasing a drag over a disabled droppable fires **no `drop` event**; the `(dragEnd)` event still fires with `cancelled: false` and `destinationIndex: null`, so consumers that pair `drop`/`dragEnd` should treat a `null` `destinationIndex` as "no valid drop target".
327
+
323
328
  ### Low-Level API
324
329
 
325
330
  For maximum control, use individual components instead of `VirtualSortableListComponent`:
@@ -446,11 +451,13 @@ export class MyComponent {
446
451
  }
447
452
 
448
453
  announceEnd(e: DragEndEvent) {
449
- this.announce(
450
- e.cancelled
451
- ? `Cancelled. Returned to position ${e.sourceIndex + 1}`
452
- : `Dropped at position ${e.destinationIndex! + 1}`,
453
- );
454
+ // destinationIndex is null when there is no valid drop target: an Escape cancel,
455
+ // a release outside every droppable, or a release over a disabled droppable.
456
+ if (e.destinationIndex === null) {
457
+ this.announce(`Returned to position ${e.sourceIndex + 1}`);
458
+ return;
459
+ }
460
+ this.announce(`Dropped at position ${e.destinationIndex + 1}`);
454
461
  }
455
462
  }
456
463
  ```
@@ -489,13 +496,22 @@ All event types are importable from `ngx-virtual-dnd`.
489
496
  | `(dragEnd)` | `DragEndEvent` | `DraggableDirective` |
490
497
  | `(drop)` | `DropEvent` | `DroppableDirective`, `VirtualSortableListComponent` |
491
498
 
492
- `DragEndEvent` includes a `cancelled` boolean to distinguish drops from cancellations.
499
+ `DragEndEvent.destinationIndex` is `null` when no drop occurred — an Escape cancel, a release outside every droppable, or a release over a disabled droppable — so branch on `destinationIndex === null` to detect that. The `cancelled` boolean is `true` only for an active Escape cancel.
493
500
 
494
501
  ## How It Works
495
502
 
496
503
  Traditional drag-and-drop libraries query sibling DOM elements via `getBoundingClientRect()`. This fails with virtual scrolling because items outside the viewport aren't rendered.
497
504
 
498
- This library uses **element-under-point detection**: temporarily hide the dragged element, use `document.elementFromPoint()` to find what's at the cursor, walk up to find the droppable, and calculate placeholder position mathematically.
505
+ This library uses **geometric hit-testing against a cached rect snapshot**: at drag start it snapshots the candidate droppables of the active group and their bounding rects, then per-pointermove it hit-tests the cursor against those rects (last match in document order wins) and calculates the placeholder position mathematically. Caching the rects avoids the forced layout flush that `document.elementFromPoint()` imposes on the hot drag loop. The snapshot self-heals during the drag: rects are re-read on scroll/resize and when a candidate resizes (via `ResizeObserver`), the candidate **list** is refreshed when a droppable mounts or unmounts mid-drag, and each rect is clipped to its nearest `vdndScrollable` ancestor so a droppable scrolled out of a clipping container is not falsely hit.
506
+
507
+ ### Known Limitations
508
+
509
+ Because hit-testing is geometry against cached rects (not live `elementFromPoint`), a few browser-native behaviors are **not** reproduced:
510
+
511
+ - **Occluding overlays** — a modal, toolbar, or other element painted over a droppable does not block a geometric hit; the droppable underneath still resolves as the target.
512
+ - **Stacking contexts / explicit `z-index`** — the "last in document order" tie-break approximates paint order but is not true painter order once `z-index` and stacking contexts are involved. An earlier-in-DOM droppable raised visually on top via `z-index` loses the tie-break to a later sibling.
513
+
514
+ If your layout depends on these, keep overlapping droppables out of the same drag group, or avoid painting interactive overlays across active drop targets during a drag.
499
515
 
500
516
  ## Browser Support
501
517
 
@@ -517,7 +533,7 @@ npm run e2e # E2E tests
517
533
  Install a skill to teach AI coding assistants how to integrate this library:
518
534
 
519
535
  ```bash
520
- npx skills add gultyayev/angular-vdnd
536
+ npx skills add gultyayev/ngx-virtual-dnd
521
537
  ```
522
538
 
523
539
  Works with Claude Code, Cursor, Windsurf, GitHub Copilot, and [40+ other agents](https://skills.sh).