Skip to content

Commit 8f58a18

Browse files
committed
feat(directive): add drag auto scroll directive
1 parent 6125341 commit 8f58a18

File tree

8 files changed

+779
-0
lines changed

8 files changed

+779
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.list {
2+
width: 400px;
3+
max-height: 350px;
4+
overflow-y: auto;
5+
border: 2px solid #1976d2;
6+
border-radius: 8px;
7+
padding: 4px;
8+
background: #e3f2fd;
9+
}
10+
11+
.item {
12+
display: flex;
13+
align-items: center;
14+
gap: 8px;
15+
padding: 10px 14px;
16+
margin: 4px 0;
17+
background: #fff;
18+
border-radius: 6px;
19+
font-size: 15px;
20+
cursor: grab;
21+
transition: box-shadow 0.2s, background 0.2s;
22+
user-select: none;
23+
24+
&:hover {
25+
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);
26+
}
27+
28+
&.dragging {
29+
opacity: 0.4;
30+
}
31+
32+
&.drag-over {
33+
border-top: 2px solid #1976d2;
34+
background: #bbdefb;
35+
}
36+
}
37+
38+
.handle {
39+
color: #90a4ae;
40+
font-size: 18px;
41+
cursor: grab;
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Component, signal } from '@angular/core';
2+
import { DragAutoScrollDirective } from 'ngx-oneforall/directives/drag-auto-scroll';
3+
4+
@Component({
5+
selector: 'lib-drag-auto-scroll-demo',
6+
imports: [DragAutoScrollDirective],
7+
template: `
8+
<div dragAutoScroll class="list">
9+
@for (item of items(); track item; let i = $index) {
10+
<div
11+
class="item"
12+
draggable="true"
13+
[class.dragging]="dragIndex() === i"
14+
[class.drag-over]="dropIndex() === i && dragIndex() !== i"
15+
(dragstart)="onDragStart(i)"
16+
(dragover)="onDragOver($event, i)"
17+
(dragend)="onDragEnd()"
18+
(drop)="onDrop($event, i)">
19+
<span class="handle">⠿</span>
20+
{{ item }}
21+
</div>
22+
}
23+
</div>
24+
`,
25+
styleUrl: './drag-auto-scroll-demo.component.scss',
26+
})
27+
export class DragAutoScrollDemoComponent {
28+
items = signal(Array.from({ length: 30 }, (_, i) => `Item ${i + 1}`));
29+
dragIndex = signal<number | null>(null);
30+
dropIndex = signal<number | null>(null);
31+
32+
onDragStart(index: number) {
33+
this.dragIndex.set(index);
34+
}
35+
36+
onDragOver(event: DragEvent, index: number) {
37+
event.preventDefault();
38+
this.dropIndex.set(index);
39+
}
40+
41+
onDrop(event: DragEvent, targetIndex: number) {
42+
event.preventDefault();
43+
const from = this.dragIndex();
44+
if (from === null || from === targetIndex) return;
45+
46+
const updated = [...this.items()];
47+
const [moved] = updated.splice(from, 1);
48+
updated.splice(targetIndex, 0, moved);
49+
this.items.set(updated);
50+
51+
this.dragIndex.set(null);
52+
this.dropIndex.set(null);
53+
}
54+
55+
onDragEnd() {
56+
this.dragIndex.set(null);
57+
this.dropIndex.set(null);
58+
}
59+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
![Bundle Size](https://deno.bundlejs.com/badge?q=ngx-oneforall/directives/drag-auto-scroll&treeshake=[*]&config={"esbuild":{"external":["rxjs","@angular/core","@angular/common","@angular/forms","@angular/router"]}})
2+
3+
Automatically scrolls a container when a dragged item approaches the top or bottom edge. Apply to any scrollable element used with drag-and-drop.
4+
5+
## Features
6+
7+
- **CDK & Native Compatible** — Listens on `document` so it works with CDK drag-drop and native HTML5 drag
8+
- **Proportional Speed** — Scrolls faster as the cursor gets closer to the edge
9+
- **Horizontal Bounds** — Only triggers when the cursor is horizontally over the container (with configurable tolerance)
10+
- **Configurable** — Customize margin zone, max speed, and horizontal tolerance
11+
- **Zone Optimized** — Runs outside Angular zone for better performance
12+
- **SSR Safe** — Only activates in the browser
13+
- **Toggleable** — Enable/disable via `dragAutoScrollDisabled` input
14+
15+
---
16+
17+
## Installation
18+
19+
```typescript
20+
import { DragAutoScrollDirective } from 'ngx-oneforall/directives/drag-auto-scroll';
21+
```
22+
23+
---
24+
25+
## Basic Usage
26+
27+
```html
28+
<ul dragAutoScroll style="overflow-y: auto; height: 400px">
29+
@for (item of items; track $index) {
30+
<li draggable="true">{{ item }}</li>
31+
}
32+
</ul>
33+
```
34+
35+
---
36+
37+
## API Reference
38+
39+
| Input | Type | Default | Description |
40+
|---|---|---|---|
41+
| `dragAutoScrollMargin` | `number` | `50` | Edge zone height (px) that triggers scrolling |
42+
| `dragAutoScrollSpeed` | `number` | `10` | Maximum scroll speed (px/frame) |
43+
| `dragAutoScrollTolerance` | `number` | `50` | Horizontal tolerance (px) outside container bounds above which scrolling does not trigger |
44+
| `dragAutoScrollDisabled` | `boolean` | `false` | Disables auto-scroll behavior |
45+
46+
### `dragAutoScrollMargin`
47+
48+
Defines the height (in pixels) of the invisible zone at the top and bottom edges of the container. When the cursor enters this zone during a drag, scrolling begins. Larger values make it easier to trigger scrolling, smaller values require the cursor to be closer to the edge.
49+
50+
### `dragAutoScrollSpeed`
51+
52+
The maximum scroll speed in pixels per animation frame. The actual speed scales proportionally — when the cursor is at the very edge, it scrolls at full speed; at the outer boundary of the margin zone, it scrolls slowly. Increase for long lists where users need to scroll quickly.
53+
54+
### `dragAutoScrollTolerance`
55+
56+
How far (in pixels) the cursor can be **horizontally** outside the container's left/right edges before scrolling stops. This prevents accidental scrolling when the cursor drifts sideways during a drag. Set to `0` to require the cursor to be strictly inside the container horizontally.
57+
58+
### `dragAutoScrollDisabled`
59+
60+
When `true`, the directive stops responding to drag events entirely. Useful for conditionally disabling auto-scroll based on application state.
61+
62+
---
63+
64+
## Custom Configuration
65+
66+
Increase the edge zone and scroll speed for large lists:
67+
68+
```html
69+
<div
70+
dragAutoScroll
71+
[dragAutoScrollMargin]="80"
72+
[dragAutoScrollSpeed]="15"
73+
style="overflow-y: auto; max-height: 600px"
74+
>
75+
<!-- draggable content -->
76+
</div>
77+
```
78+
79+
---
80+
81+
## How It Works
82+
83+
1. The directive listens for `dragover` events on `document` (works even when a drag preview covers the container)
84+
2. It checks the cursor is horizontally within the container bounds (± tolerance)
85+
3. When the cursor enters the top or bottom margin zone, scrolling starts via `requestAnimationFrame`
86+
4. Scroll speed scales linearly — the closer to the edge, the faster it scrolls
87+
5. Scrolling stops on `drop`, `dragend`, or when the cursor moves to the center or outside bounds
88+
89+
---
90+
91+
## Live Demo
92+
93+
{{ NgDocActions.demoPane("DragAutoScrollDemoComponent") }}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NgDocPage } from '@ng-doc/core';
2+
import DirectivesCategory from '../../ng-doc.category';
3+
import { DragAutoScrollDemoComponent } from './demo/drag-auto-scroll-demo/drag-auto-scroll-demo.component';
4+
5+
const DragAutoScrollDirective: NgDocPage = {
6+
title: 'Drag Auto Scroll',
7+
mdFile: './index.md',
8+
category: DirectivesCategory,
9+
demos: {
10+
DragAutoScrollDemoComponent,
11+
},
12+
};
13+
14+
export default DragAutoScrollDirective;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "lib": { "entryFile": "src/public_api.ts" } }

0 commit comments

Comments
 (0)