ngx-virtual-dnd 3.1.3 → 3.2.0-alpha.1

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
@@ -1,23 +1,18 @@
1
1
  # ngx-virtual-dnd
2
2
 
3
- Angular drag-and-drop library optimized for virtual scrolling. Handles thousands of items efficiently by only rendering visible elements.
3
+ Angular drag and drop for virtual scrolling. Sortable lists and boards that stay fast with thousands of items: only the visible rows are rendered, and drag and drop still works across all of them.
4
4
 
5
- Inspired by [react-virtualized-dnd](https://github.com/forecast-it/react-virtualized-dnd/).
5
+ **[Documentation](https://gultyayev.github.io/ngx-virtual-dnd/)** · **[Live demo](https://gultyayev.github.io/ngx-virtual-dnd/demo/)** · **[Changelog](https://gultyayev.github.io/ngx-virtual-dnd/changelog.html)**
6
6
 
7
7
  ## Features
8
8
 
9
- - **Virtual Scrolling** - Renders only visible items plus overscan buffer
10
- - **Dynamic Item Heights** - Auto-measured via ResizeObserver with O(log N) lookups
11
- - **Smooth Drag & Drop** - 60fps with RAF throttling
12
- - **Cross-List Support** - Drag between multiple lists with group filtering
13
- - **Auto-Scroll** - Configurable edge scrolling with speed/threshold control
14
- - **Drag Handles** - Restrict drag initiation to specific elements
15
- - **Container Constraints** - Constrain drag preview to container boundaries
16
- - **Axis Locking** - Lock dragging to horizontal or vertical axis
17
- - **Custom Previews** - Template-based drag preview and placeholder customization
18
- - **Keyboard Accessible** - Space to grab, arrows to move, Escape to cancel
19
- - **Touch Support** - Works with mouse and touch, with configurable delay/threshold
20
- - **Angular 21+** - Signals, standalone components, modern patterns
9
+ - Virtual scrolling with fixed or dynamic (auto-measured) item heights
10
+ - Drag between multiple lists, with edge auto-scroll
11
+ - Page-level scrolling, including Ionic `ion-content`
12
+ - Drag handles, delays, axis locking, container constraints, custom previews
13
+ - Opt-in shift animations, plus a per-step event for haptics
14
+ - Keyboard dragging and touch support
15
+ - Angular 21+: standalone components and signals
21
16
 
22
17
  ## Installation
23
18
 
@@ -25,19 +20,27 @@ Inspired by [react-virtualized-dnd](https://github.com/forecast-it/react-virtual
25
20
  npm install ngx-virtual-dnd
26
21
  ```
27
22
 
28
- ## Quick Start
23
+ ## Example
29
24
 
30
25
  ```typescript
26
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
31
27
  import {
32
- VirtualSortableListComponent,
33
- DroppableGroupDirective,
34
- DraggableDirective,
35
28
  DragPreviewComponent,
29
+ DraggableDirective,
36
30
  DropEvent,
37
- moveItem,
31
+ DroppableGroupDirective,
32
+ reorderItems,
33
+ VirtualSortableListComponent,
38
34
  } from 'ngx-virtual-dnd';
39
35
 
36
+ interface Task {
37
+ id: string;
38
+ title: string;
39
+ }
40
+
40
41
  @Component({
42
+ selector: 'app-tasks',
43
+ changeDetection: ChangeDetectionStrategy.OnPush,
41
44
  imports: [
42
45
  VirtualSortableListComponent,
43
46
  DroppableGroupDirective,
@@ -45,499 +48,69 @@ import {
45
48
  DragPreviewComponent,
46
49
  ],
47
50
  template: `
48
- <!-- Item template -->
49
- <ng-template #itemTpl let-item>
50
- <div class="item" [vdndDraggable]="item.id" [vdndDraggableData]="item">
51
- {{ item.name }}
52
- </div>
53
- </ng-template>
54
-
55
- <!-- Lists wrapped in a group -->
56
- <div vdndGroup="my-group">
57
- <vdnd-sortable-list
58
- droppableId="list-1"
59
- group="my-group"
60
- [items]="list1()"
61
- [itemHeight]="50"
62
- [containerHeight]="400"
63
- [itemIdFn]="getItemId"
64
- [itemTemplate]="itemTpl"
65
- (drop)="onDrop($event)"
66
- />
51
+ <div vdndGroup="tasks">
52
+ <ng-template #taskTpl let-task>
53
+ <div class="row" [vdndDraggable]="task.id">{{ task.title }}</div>
54
+ </ng-template>
67
55
 
68
56
  <vdnd-sortable-list
69
- droppableId="list-2"
70
- group="my-group"
71
- [items]="list2()"
72
- [itemHeight]="50"
57
+ droppableId="backlog"
58
+ [items]="tasks()"
59
+ [itemHeight]="48"
73
60
  [containerHeight]="400"
74
- [itemIdFn]="getItemId"
75
- [itemTemplate]="itemTpl"
61
+ [itemIdFn]="taskId"
62
+ [itemTemplate]="taskTpl"
76
63
  (drop)="onDrop($event)"
77
64
  />
78
65
  </div>
79
66
 
80
- <!-- Required: renders the dragged item preview -->
81
67
  <vdnd-drag-preview />
82
68
  `,
83
69
  })
84
- export class MyComponent {
85
- list1 = signal<Item[]>([...]);
86
- list2 = signal<Item[]>([...]);
70
+ export class TasksComponent {
71
+ readonly tasks = signal<Task[]>(
72
+ Array.from({ length: 1000 }, (_, i) => ({ id: `task-${i + 1}`, title: `Task ${i + 1}` })),
73
+ );
87
74
 
88
- getItemId = (item: Item) => item.id;
75
+ readonly taskId = (task: Task): string => task.id;
89
76
 
90
77
  onDrop(event: DropEvent): void {
91
- moveItem(event, {
92
- 'list-1': this.list1,
93
- 'list-2': this.list2,
94
- });
78
+ reorderItems(event, this.tasks);
95
79
  }
96
80
  }
97
81
  ```
98
82
 
99
- **That's it!** `VirtualSortableListComponent` handles placeholder positioning, sticky items during drag, and virtual scroll integration automatically.
100
-
101
- ## API Overview
102
-
103
- The library exports these main pieces (use IDE completion for full details):
104
-
105
- **Components:**
106
-
107
- - `VirtualSortableListComponent` - High-level component combining droppable, virtual scroll, and placeholder
108
- - `VirtualScrollContainerComponent` - Low-level virtual scroll container
109
- - `VirtualViewportComponent` - Self-contained virtual scroll viewport
110
- - `VirtualContentComponent` - Virtual content for external scroll containers (page-level scroll)
111
- - `DragPreviewComponent` - Renders the dragged item preview (required, supports custom templates)
112
- - `PlaceholderComponent` - Drop position indicator
113
-
114
- **Directives:**
115
-
116
- - `DraggableDirective` (`vdndDraggable`) - Makes an element draggable (supports drag handles, axis locking, threshold/delay)
117
- - `DroppableDirective` (`vdndDroppable`) - Marks a drop target (supports container constraints, auto-scroll config)
118
- - `DroppableGroupDirective` (`vdndGroup`) - Provides group context to children
119
- - `ScrollableDirective` (`vdndScrollable`) - Marks external scroll container
120
- - `VirtualForDirective` (`*vdndVirtualFor`) - Structural directive for virtual lists
121
- - `ContentHeaderDirective` (`vdndContentHeader`) - Marks a projected header inside `VirtualContentComponent` (auto-measured via ResizeObserver)
122
-
123
- **Services:**
124
-
125
- - `DragStateService` - Access drag state (isDragging, draggedItem, placeholderIndex, etc.)
126
- - `AutoScrollService` - Controls edge auto-scrolling
127
- - `PositionCalculatorService` - Calculates placeholder positions
128
-
129
- **Utilities:**
130
-
131
- - `moveItem()` - Move between signal-based lists
132
- - `reorderItems()` - Reorder within a single list
133
- - `applyMove()` - Immutable version (returns new arrays)
134
- - `isNoOpDrop()` - Check if drop would be a no-op
135
- - `insertAt()` / `removeAt()` - Low-level array helpers
136
-
137
- **Strategies:**
138
-
139
- - `VirtualScrollStrategy` - Interface for custom virtual scroll strategies
140
- - `FixedHeightStrategy` - Fixed `index * itemHeight` math (zero overhead)
141
- - `DynamicHeightStrategy` - Variable heights with auto-measurement and binary search
142
-
143
- ## Advanced Usage
144
-
145
- ### Dynamic Item Heights
146
-
147
- When items have variable heights, enable `dynamicItemHeight`. Items are auto-measured via ResizeObserver — no manual height tracking needed. The `itemHeight` value serves as the initial estimate for unmeasured items.
148
-
149
- **With `VirtualSortableListComponent`:**
150
-
151
- ```html
152
- <vdnd-sortable-list
153
- droppableId="list-1"
154
- group="my-group"
155
- [items]="list()"
156
- [itemHeight]="80"
157
- [dynamicItemHeight]="true"
158
- [itemIdFn]="getItemId"
159
- [itemTemplate]="itemTpl"
160
- (drop)="onDrop($event)"
161
- />
162
- ```
163
-
164
- **With `VirtualScrollContainerComponent`:**
165
-
166
- ```html
167
- <vdnd-virtual-scroll
168
- [items]="items()"
169
- [itemHeight]="80"
170
- [dynamicItemHeight]="true"
171
- [itemIdFn]="getItemId"
172
- [trackByFn]="trackById"
173
- [itemTemplate]="itemTpl"
174
- />
175
- ```
176
-
177
- **With `VirtualForDirective`:**
178
-
179
- ```html
180
- <ng-container
181
- *vdndVirtualFor="
182
- let item of items();
183
- itemHeight: 80;
184
- dynamicItemHeight: true;
185
- trackBy: trackById;
186
- droppableId: 'list-1'
187
- "
188
- >
189
- <div class="item">{{ item.description }}</div>
190
- </ng-container>
191
- ```
192
-
193
- Notes:
194
-
195
- - `FixedHeightStrategy` is used by default when `dynamicItemHeight` is not set
196
- - Setting `dynamicItemHeight` switches to `DynamicHeightStrategy` with automatic height measurement
197
- - Heights are tracked by `trackBy` key, so they survive reordering
198
- - The `itemHeight` value is used as the initial estimate for items not yet measured
199
- - Inside a viewport component (`vdnd-virtual-viewport` or `vdnd-virtual-content`), `itemHeight`, `dynamicItemHeight`, and `droppableId` are inherited automatically — only `trackBy` is needed on the directive
200
-
201
- ### Drag Handles
202
-
203
- Use `dragHandle` with a CSS selector to restrict where users can initiate a drag:
204
-
205
- ```html
206
- <div [vdndDraggable]="item.id" dragHandle=".handle">
207
- <span class="handle">⠿</span>
208
- <span>{{ item.name }}</span>
209
- </div>
210
- ```
211
-
212
- Only clicks on elements matching the selector will start a drag. The rest of the element remains interactive.
213
-
214
- ### Container Constraints
215
-
216
- Constrain the drag preview and placeholder to stay within the droppable container:
217
-
218
- ```html
219
- <vdnd-sortable-list
220
- droppableId="list-1"
221
- group="my-group"
222
- [items]="items()"
223
- [itemHeight]="50"
224
- [constrainToContainer]="true"
225
- [itemIdFn]="getItemId"
226
- [itemTemplate]="itemTpl"
227
- (drop)="onDrop($event)"
228
- />
229
- ```
230
-
231
- Or on the directive directly:
232
-
233
- ```html
234
- <div vdndDroppable="list-1" [constrainToContainer]="true">...</div>
235
- ```
236
-
237
- The preview cannot leave the droppable area, and the placeholder snaps to the edges.
238
-
239
- ### Axis Locking
240
-
241
- Lock dragging to a single axis:
242
-
243
- ```html
244
- <!-- Lock the Y axis → item moves horizontally only -->
245
- <div [vdndDraggable]="item.id" lockAxis="y">{{ item.name }}</div>
246
- ```
247
-
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.
251
-
252
- ### Drag Threshold & Delay
253
-
254
- ```html
255
- <div [vdndDraggable]="item.id" [dragThreshold]="10" [dragDelay]="200">{{ item.name }}</div>
256
- ```
257
-
258
- - `dragThreshold` — minimum distance (px) before drag starts (default: `5`). Prevents accidental drags on click.
259
- - `dragDelay` — delay (ms) after pointer down before drag activates (default: `0`). Useful on touch devices to distinguish scrolling from dragging. Use the `vdnd-drag-pending` CSS class to show visual feedback when the delay passes.
260
-
261
- ### Custom Drag Preview
262
-
263
- Provide a custom template for the drag preview:
264
-
265
- ```html
266
- <ng-template #preview let-data let-draggableId="draggableId">
267
- <div class="custom-preview">Dragging: {{ data.name }}</div>
268
- </ng-template>
269
-
270
- <vdnd-drag-preview [previewTemplate]="preview" [cursorOffset]="{ x: 16, y: 16 }" />
271
- ```
272
-
273
- - `previewTemplate` — custom template for the preview. Context provides `$implicit` (the draggable's data), `draggableId`, and `droppableId`.
274
- - `cursorOffset` — offset from cursor in pixels (default: `{ x: 8, y: 8 }`).
275
-
276
- Without a custom template, the library clones the dragged element as the preview.
277
-
278
- ### Auto-Scroll Configuration
279
-
280
- Configure auto-scroll behavior when dragging near container edges:
281
-
282
- ```html
283
- <vdnd-sortable-list
284
- droppableId="list-1"
285
- group="my-group"
286
- [items]="items()"
287
- [itemHeight]="50"
288
- [autoScrollConfig]="{ threshold: 80, maxSpeed: 20 }"
289
- [itemIdFn]="getItemId"
290
- [itemTemplate]="itemTpl"
291
- (drop)="onDrop($event)"
292
- />
293
- ```
294
-
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 |
300
-
301
- Set `[autoScrollEnabled]="false"` to disable auto-scroll entirely. These options are available on `VirtualSortableListComponent`, `DroppableDirective`, and `ScrollableDirective`.
302
-
303
- ### Disabling Drag & Drop
304
-
305
- Use the `disabled` input to conditionally disable draggables, droppables, or entire lists:
306
-
307
- ```html
308
- <!-- Disable a single item -->
309
- <div [vdndDraggable]="item.id" [disabled]="!item.canDrag">{{ item.name }}</div>
310
-
311
- <!-- Disable an entire list -->
312
- <vdnd-sortable-list
313
- droppableId="list-1"
314
- group="my-group"
315
- [items]="items()"
316
- [itemHeight]="50"
317
- [disabled]="isReadOnly()"
318
- [itemIdFn]="getItemId"
319
- [itemTemplate]="itemTpl"
320
- (drop)="onDrop($event)"
321
- />
322
- ```
323
-
324
- Disabled draggables get the `vdnd-draggable-disabled` CSS class. Disabled droppables get `vdnd-droppable-disabled`.
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
-
328
- ### Low-Level API
329
-
330
- For maximum control, use individual components instead of `VirtualSortableListComponent`:
331
-
332
- ```typescript
333
- @Component({
334
- imports: [
335
- VirtualScrollContainerComponent,
336
- DroppableGroupDirective,
337
- DroppableDirective,
338
- DraggableDirective,
339
- DragPreviewComponent,
340
- ],
341
- template: `
342
- <ng-template #itemTpl let-item>
343
- <div class="item" [vdndDraggable]="item.id" [vdndDraggableData]="item">
344
- {{ item.name }}
345
- </div>
346
- </ng-template>
347
-
348
- <div vdndGroup="demo">
349
- <div vdndDroppable="list-1" (drop)="onDrop($event)">
350
- <vdnd-virtual-scroll
351
- droppableId="list-1"
352
- [items]="items()"
353
- [itemHeight]="50"
354
- [itemIdFn]="getItemId"
355
- [trackByFn]="trackById"
356
- [itemTemplate]="itemTpl"
357
- />
358
- </div>
359
- </div>
360
- <vdnd-drag-preview />
361
- `,
362
- })
363
- export class ListComponent {
364
- items = signal<Item[]>([]);
365
- }
366
- ```
367
-
368
- ### Page-Level Scroll
369
-
370
- Use `VirtualContentComponent` with `vdndScrollable` for page-level scrolling with headers/footers:
371
-
372
- ```typescript
373
- @Component({
374
- imports: [
375
- ScrollableDirective,
376
- VirtualContentComponent,
377
- VirtualForDirective,
378
- DraggableDirective,
379
- DroppableDirective,
380
- DroppableGroupDirective,
381
- DragPreviewComponent,
382
- ContentHeaderDirective,
383
- ],
384
- template: `
385
- <ion-content [scrollY]="false">
386
- <div class="scroll-container ion-content-scroll-host" vdndScrollable>
387
- <div vdndGroup="tasks">
388
- <vdnd-virtual-content
389
- [itemHeight]="72"
390
- vdndDroppable="list-1"
391
- (drop)="onDrop($event)"
392
- >
393
- <!-- Header — auto-measured via ResizeObserver, scrolls with content -->
394
- <div class="header" vdndContentHeader>Welcome!</div>
395
-
396
- <ng-container
397
- *vdndVirtualFor="
398
- let item of items();
399
- trackBy: trackById
400
- "
401
- >
402
- <div class="item" [vdndDraggable]="item.id">{{ item.name }}</div>
403
- </ng-container>
404
-
405
- </vdnd-virtual-content>
406
- </div>
407
-
408
- <!-- Footer — normal sibling in document flow -->
409
- <div class="footer">Load more</div>
410
- </div>
411
- </ion-content>
412
-
413
- <vdnd-drag-preview />
414
- `,
415
- })
416
- export class PageComponent {
417
- items = signal<Item[]>([...]);
418
- }
419
- ```
420
-
421
- Key points:
422
-
423
- - `vdndScrollable` marks the scroll container
424
- - `VirtualContentComponent` provides wrapper-based positioning and derives total items from the child `*vdndVirtualFor` automatically
425
- - `vdndContentHeader` marks a projected header — its height is auto-measured via ResizeObserver and used as the content offset (no manual measurement needed)
426
- - `contentOffset` input is available as an escape hatch when the header lives outside the component
427
- - `*vdndVirtualFor` inherits `itemHeight`, `dynamicItemHeight`, and `droppableId` from the parent viewport/droppable — only `trackBy` is required
83
+ Every draggable and droppable needs a group (here from `vdndGroup`), even for a single list, and the item template must be declared inside it. The [Quick start](https://gultyayev.github.io/ngx-virtual-dnd/guide/start/quick-start.html) explains each part, and [Core concepts](https://gultyayev.github.io/ngx-virtual-dnd/guide/essentials/core-concepts.html) lists the rules that keep a setup working.
428
84
 
429
- ### Screen Reader Announcements
85
+ ## Documentation
430
86
 
431
- The library emits events with position data. Implement announcements in your app:
87
+ - [Getting started](https://gultyayev.github.io/ngx-virtual-dnd/guide/start/introduction.html): introduction, installation, quick start
88
+ - [Guides](https://gultyayev.github.io/ngx-virtual-dnd/guide/essentials/core-concepts.html): multiple lists, dynamic heights, page scroll, drag behavior, styling, accessibility
89
+ - [API reference](https://gultyayev.github.io/ngx-virtual-dnd/api/components.html): components, directives, events, utilities, services, types
432
90
 
433
- ```typescript
434
- @Component({
435
- template: `
436
- <div
437
- vdndDraggable="item-1"
438
- (dragStart)="announce('Grabbed ' + item.name)"
439
- (dragEnd)="announceEnd($event)"
440
- >
441
- {{ item.name }}
442
- </div>
443
- <div aria-live="assertive" class="sr-only">{{ announcement() }}</div>
444
- `,
445
- })
446
- export class MyComponent {
447
- announcement = signal('');
91
+ ## AI agent skills
448
92
 
449
- announce(msg: string) {
450
- this.announcement.set(msg);
451
- }
93
+ Install a skill that teaches AI coding assistants how to integrate this library:
452
94
 
453
- announceEnd(e: DragEndEvent) {
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}`);
461
- }
462
- }
95
+ ```bash
96
+ npx skills add gultyayev/ngx-virtual-dnd
463
97
  ```
464
98
 
465
- ## Keyboard Navigation
466
-
467
- | Key | Action |
468
- | ----------- | --------------------------- |
469
- | `Tab` | Navigate to draggable items |
470
- | `Space` | Start/end drag |
471
- | `Arrow ↑/↓` | Move item up/down |
472
- | `Arrow ←/→` | Move to adjacent list |
473
- | `Escape` | Cancel drag |
474
-
475
- ARIA attributes (`aria-grabbed`, `aria-dropeffect`, `tabindex`) are managed automatically.
476
-
477
- ## CSS Classes
478
-
479
- | Class | Applied When |
480
- | ------------------------- | --------------------------- |
481
- | `vdnd-draggable` | Always on draggable |
482
- | `vdnd-draggable-dragging` | While being dragged |
483
- | `vdnd-draggable-disabled` | When disabled |
484
- | `vdnd-drag-pending` | After delay, ready to drag |
485
- | `vdnd-droppable` | Always on droppable |
486
- | `vdnd-droppable-active` | When a draggable is over it |
487
- | `vdnd-droppable-disabled` | When disabled |
488
-
489
- ## Events
490
-
491
- All event types are importable from `ngx-virtual-dnd`.
492
-
493
- | Output | Event Type | Emitted By |
494
- | ------------- | ---------------- | ---------------------------------------------------- |
495
- | `(dragStart)` | `DragStartEvent` | `DraggableDirective` |
496
- | `(dragEnd)` | `DragEndEvent` | `DraggableDirective` |
497
- | `(drop)` | `DropEvent` | `DroppableDirective`, `VirtualSortableListComponent` |
498
-
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.
500
-
501
- ## How It Works
502
-
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.
504
-
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.
515
-
516
- ## Browser Support
517
-
518
- - Chrome/Edge (latest)
519
- - Firefox (latest)
520
- - Safari (latest)
99
+ It works with Claude Code, Cursor, Windsurf, GitHub Copilot and [40+ other agents](https://skills.sh). The docs are also published as [llms.txt](https://gultyayev.github.io/ngx-virtual-dnd/llms.txt).
521
100
 
522
101
  ## Development
523
102
 
524
- ```bash
525
- npm start # Dev server (localhost:4200)
526
- ng build ngx-virtual-dnd # Build library (required after lib edits)
527
- npm test # Unit tests
528
- npm run e2e # E2E tests
529
- ```
530
-
531
- ## AI Agent Skills
532
-
533
- Install a skill to teach AI coding assistants how to integrate this library:
103
+ Requires npm 12 (the version pinned in `packageManager`): `npm install -g npm@12`.
534
104
 
535
105
  ```bash
536
- npx skills add gultyayev/ngx-virtual-dnd
106
+ npm start # Demo app (localhost:4200)
107
+ npm run build:lib # Build the library (required after library edits)
108
+ npm run docs:dev # Docs site (localhost:3000); live examples load from the demo on :4200
109
+ npm test # Unit tests
110
+ npm run e2e # E2E tests
111
+ npm run site:build # Full GitHub Pages build: docs at /, demo at /demo/
537
112
  ```
538
113
 
539
- Works with Claude Code, Cursor, Windsurf, GitHub Copilot, and [40+ other agents](https://skills.sh).
540
-
541
114
  ## License
542
115
 
543
116
  MIT