@tft/interact 21.0.7 → 21.1.2

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,42 +1,337 @@
1
+ # @tft/interact
1
2
 
2
- # tft/interact
3
+ An Angular directive library wrapping [interactjs](https://interactjs.io/) for drag-and-drop, resize, gesture, auto-scroll, and lasso selection.
3
4
 
4
- ## An Angular plugin for the interactjs library
5
-
6
- We aim to build a versatile API that exposes all the features of interactjs, but also simplifies implementing common behaviors.
5
+ ---
7
6
 
8
7
  ## Installation
9
8
 
10
- `npm i interactjs @tft/interact --save`
9
+ ```bash
10
+ npm install @tft/interact interactjs
11
+ ```
12
+
13
+ All directives are **standalone** — import them individually or use `InteractModule` for NgModule-based apps.
14
+
15
+ **Standalone (recommended):**
16
+ ```typescript
17
+ import { DraggableDirective, DropzoneDirective, ResizableDirective } from '@tft/interact';
18
+
19
+ @Component({
20
+ imports: [DraggableDirective, DropzoneDirective, ResizableDirective],
21
+ })
22
+ export class MyComponent {}
23
+ ```
24
+
25
+ **NgModule (legacy):**
26
+ ```typescript
27
+ import { InteractModule } from '@tft/interact';
11
28
 
12
- ## Usage
29
+ @NgModule({
30
+ imports: [InteractModule.forRoot({ cssDimensionUnit: 'px' })],
31
+ })
32
+ export class AppModule {}
33
+ ```
34
+
35
+ > `InteractModule` is deprecated and will be removed in the next major version. Prefer importing directives directly.
36
+
37
+ ---
38
+
39
+ ## Directives
13
40
 
14
- Interactive documentation can be found on [Stackblitz](https://stackblitz.com/github/nayfin/tft-documentation?file=src%2Fapp%2Finteract-demo%2Flandscaping%2Fcontainers%2Fyard%2Fyard.component.html).
41
+ | Directive | Selector | Purpose |
42
+ |---|---|---|
43
+ | `DraggableDirective` | `[tftDraggable]` | Makes an element draggable |
44
+ | `DropzoneDirective` | `[tftDropzone]` | Defines a drop target |
45
+ | `ResizableDirective` | `[tftResizable]` | Makes an element resizable |
46
+ | `GesturableDirective` | `[tftGesturable]` | Pinch/rotate gesture support |
47
+ | `DragRootDirective` | `[tftDragRoot]` | Sets where the drag clone is appended |
48
+ | `AccountForScaleDirective` | `[tftAccountForScale]` | Compensates coordinates for CSS `scale()` |
49
+ | `ApplyScaleDirective` | `[tftApplyScale]` | Applies a scale value as `font-size` |
50
+ | `AutoScrollDirective` | `[tftAutoScroll]` | Auto-scrolls a container during drag |
51
+ | `LassoDirective` | `[tftLasso]` | Drag-to-select rectangle on a canvas |
52
+ | `DragPreviewDirective` | `ng-template[tftDragPreview]` | Custom template for the drag clone |
53
+ | `ArrayOfNPipe` | `arrayOfN` | Creates an array of N items for `@for` |
15
54
 
16
- Here are some basic examples
17
- ### Draggables
55
+ ---
56
+
57
+ ## Draggable
18
58
 
19
59
  ```html
20
60
  <div tftDraggable
21
- dragConfig="dragConfig"
22
- [x]="x"
23
- [y]="y">
61
+ [x]="item.x" [y]="item.y"
62
+ [dragData]="item"
63
+ [dragDisabled]="isLocked"
64
+ (dragEnd)="onDragEnd($event)">
65
+ </div>
66
+ ```
67
+
68
+ **Key inputs:**
69
+
70
+ | Input | Type | Default | Description |
71
+ |---|---|---|---|
72
+ | `x` | `number` | — | Initial x position |
73
+ | `y` | `number` | — | Initial y position |
74
+ | `dragData` | `D` | — | Payload attached to drag events |
75
+ | `dragDisabled` | `boolean` | `false` | Disables drag |
76
+ | `dragZIndex` | `number` | `10000` | z-index while dragging |
77
+ | `dragConfig` | `DraggableOptions` | — | Raw interactjs config (inertia, modifiers, etc.) |
78
+ | `showPlaceholder` | `boolean` | `false` | Keep original element visible while dragging |
79
+ | `dragRoot` | `DragRootDirective` | — | Override drag root element |
80
+ | `enableDragDefault` | `boolean` | `true` | Use built-in clone+position behaviour |
81
+
82
+ **Outputs:** `dragStart`, `dragMove`, `dragInertiaStart`, `dragEnd` — all emit `TftDragEvent`.
83
+
84
+ **Inertia + restrict example:**
85
+ ```typescript
86
+ dragConfig: DraggableOptions = {
87
+ inertia: { resistance: 2, allowResume: true, endSpeed: 10, smoothEndDuration: 2000 },
88
+ modifiers: [
89
+ interact.modifiers.restrict({
90
+ restriction: '.board',
91
+ endOnly: false,
92
+ elementRect: { top: 0, left: 0, bottom: 1, right: 1 },
93
+ }),
94
+ ],
95
+ };
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Dropzone
101
+
102
+ ```html
103
+ <div tftDropzone
104
+ [dropzoneId]="'yard'"
105
+ [dropzoneConfig]="{ overlap: 0.5 }"
106
+ (dragDrop)="onDrop($event)"
107
+ (dragEnter)="onEnter($event)"
108
+ (dragLeave)="onLeave($event)">
24
109
  </div>
25
110
  ```
26
111
 
27
- Any config that will work with with interactjs will work here
28
- ```ts
29
- dragConfig = {
30
- enabled = true,
31
- lockAxis: 'y'
112
+ **Key inputs:** `dropzoneId`, `dropzoneConfig` (`DropzoneOptions`), `dropzoneData`, `position` (CSS position, default `'relative'`).
113
+
114
+ **Outputs:** `dragDrop`, `dragEnter`, `dragLeave`, `dropActivate` — all emit `TftDropEvent`.
115
+
116
+ ```typescript
117
+ onDrop(event: TftDropEvent) {
118
+ if (event.dropTarget?.dropzoneId() === 'yard') { ... }
119
+ const data = event.dragRef.dragData();
32
120
  }
33
121
  ```
34
- ### Resizable
122
+
123
+ ---
124
+
125
+ ## Resizable
35
126
 
36
127
  ```html
37
- <div tftResizable></div>
128
+ <div tftResizable
129
+ [width]="w" [height]="h"
130
+ [resizeConfig]="{ edges: { right: true, bottom: true } }"
131
+ (resizeEnd)="onResizeEnd($event)">
132
+ </div>
38
133
  ```
39
134
 
40
- ### News
41
- - gesture support in beta
42
- - Stackblitz Examples
135
+ **Key inputs:** `width`, `height`, `resizeConfig` (`ResizableOptions`), `resizeDisabled`, `position`, `enableResizeDefault`.
136
+
137
+ **Outputs:** `resizeStart`, `resizeMove`, `resizeInertiaStart`, `resizeEnd` — all emit `TftResizeEvent`.
138
+
139
+ `TftResizeEvent` includes `size: { width, height }` and `positionInDropTarget`.
140
+
141
+ ---
142
+
143
+ ## Scale Support
144
+
145
+ Use `tftAccountForScale` on the drag root when CSS `scale()` is applied:
146
+
147
+ ```html
148
+ <div tftDragRoot tftAccountForScale [scale]="scale">
149
+ <div tftDraggable tftResizable [width]="w" [height]="h"></div>
150
+ </div>
151
+ ```
152
+
153
+ For cross-dropzone dragging where the draggable is not a child of the drag root:
154
+ ```html
155
+ <div tftDragRoot tftAccountForScale [scale]="scale" #root="tftDragRoot">
156
+ <div tftDropzone>
157
+ <div tftDraggable [dragRoot]="root"></div>
158
+ </div>
159
+ <div tftDropzone (dragDrop)="onDrop($event)"></div>
160
+ </div>
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Gesture (pinch-to-zoom)
166
+
167
+ ```html
168
+ <div tftDragRoot tftGesturable tftAccountForScale
169
+ [scale]="scale"
170
+ [style.transform]="'scale(' + scale + ')'"
171
+ [style.transform-origin]="transformOrigin"
172
+ (gestureMove)="onGestureMove($event)">
173
+ </div>
174
+ ```
175
+
176
+ ```typescript
177
+ onGestureMove(event: TftGestureEvent) {
178
+ this.scale += event.interactEvent?.ds || 0; // ds = delta scale
179
+ const { x0, y0 } = event.interactEvent;
180
+ this.transformOrigin = `${x0}px ${y0}px`;
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Auto-Scroll
187
+
188
+ ```html
189
+ <div class="scroll-container" tftAutoScroll>
190
+ <div tftDragRoot>
191
+ <div tftDraggable></div>
192
+ </div>
193
+ </div>
194
+ ```
195
+
196
+ Configure scroll sensitivity via `[autoScrollConfig]`:
197
+ ```typescript
198
+ autoScrollConfig = { margin: 50, distance: 3, interval: 50, speed: 300, enabled: true };
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Drag Preview Template
204
+
205
+ ```html
206
+ <div tftDraggable [dragData]="item">
207
+ <ng-template tftDragPreview>
208
+ <div class="drag-ghost">{{ item.name }}</div>
209
+ </ng-template>
210
+ Original content
211
+ </div>
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Lasso (Drag-to-Select)
217
+
218
+ `tftLasso` tracks a pointer drag on a background canvas and emits the selection rectangle. Hit-testing against child elements is the consumer's responsibility.
219
+
220
+ ```html
221
+ <div tftLasso #lasso="tftLasso" [scale]="scale"
222
+ (lassoStart)="clearSelection()"
223
+ (lassoCommit)="onLassoCommit($event)">
224
+
225
+ @if (lasso.lassoDisplayRect(); as r) {
226
+ <div class="lasso-rect"
227
+ [style.left.px]="r.x" [style.top.px]="r.y"
228
+ [style.width.px]="r.w" [style.height.px]="r.h">
229
+ </div>
230
+ }
231
+ </div>
232
+ ```
233
+
234
+ ```typescript
235
+ onLassoCommit(rect: LassoRect) {
236
+ // rect is in viewport (clientX/Y) space
237
+ this.selected = this.items.filter(item => {
238
+ const b = this.itemEl(item).getBoundingClientRect();
239
+ return b.left < rect.right && b.right > rect.left &&
240
+ b.top < rect.bottom && b.bottom > rect.top;
241
+ });
242
+ }
243
+ ```
244
+
245
+ **Inputs:** `scale` (default `1`), `lassoDisabled` (default `false`).
246
+ **Outputs:** `lassoStart` (`void`), `lassoCommit` (`LassoRect`).
247
+ **Exported signals:** `isLassoing`, `lassoDisplayRect` (null when not dragging).
248
+
249
+ Uses Pointer Events — works with mouse, touch, and stylus.
250
+
251
+ ---
252
+
253
+ ## Event Types
254
+
255
+ ```typescript
256
+ TftDragEvent {
257
+ interactEvent: NgDragEvent;
258
+ dragRef: DraggableDirective; // dragRef.dragData() to get payload
259
+ dragOrigin?: DropzoneDirective;
260
+ dropTarget?: DropzoneDirective;
261
+ positionInDropTarget: { x, y } | null;
262
+ }
263
+
264
+ TftDropEvent // same shape as TftDragEvent
265
+
266
+ TftResizeEvent {
267
+ interactEvent: NgResizeEvent;
268
+ resizeRef: ResizableDirective;
269
+ dragRef?: DraggableDirective;
270
+ dragOrigin?: DropzoneDirective;
271
+ dropTarget?: DropzoneDirective;
272
+ positionInDropTarget: { x, y } | null;
273
+ size: { width: number; height: number };
274
+ }
275
+
276
+ TftGestureEvent {
277
+ gestureRef: GesturableDirective;
278
+ interactEvent: GestureEvent; // .ds = delta scale, .x0/.y0 = touch origin
279
+ }
280
+
281
+ LassoRect {
282
+ left: number; right: number;
283
+ top: number; bottom: number; // all in viewport (clientX/Y) space
284
+ }
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Migration to v21.1.0
290
+
291
+ Version 21.1.0 converted all directives to **standalone** and replaced `@Input()`/`@Output()` with signal-based `input()`/`output()`.
292
+
293
+ ### Import directives directly
294
+
295
+ ```typescript
296
+ // BEFORE
297
+ @NgModule({ imports: [InteractModule.forRoot({ cssDimensionUnit: 'px' })] })
298
+
299
+ // AFTER
300
+ @Component({ imports: [DraggableDirective, DropzoneDirective, ResizableDirective, ...] })
301
+ ```
302
+
303
+ `InteractModule` still works but is deprecated. `InteractService` is `providedIn: 'root'`.
304
+
305
+ ### `dragData` and `dropzoneData` are now signals
306
+
307
+ ```typescript
308
+ // BEFORE
309
+ const team = event.dragRef.dragData.team;
310
+ const points = event.dropTarget.dropzoneData.points;
311
+
312
+ // AFTER
313
+ const team = event.dragRef.dragData().team;
314
+ const points = event.dropTarget.dropzoneData().points;
315
+ ```
316
+
317
+ ### `dropzoneId` is now a signal
318
+
319
+ ```typescript
320
+ // BEFORE
321
+ if (event.dropTarget?.dropzoneId === 'yard') { ... }
322
+
323
+ // AFTER
324
+ if (event.dropTarget?.dropzoneId() === 'yard') { ... }
325
+ ```
326
+
327
+ ### `autoScrollConfig` and `scale` are now signals
328
+
329
+ ```typescript
330
+ // BEFORE
331
+ const config = this.autoScrollDir.autoScrollConfig;
332
+ const scale = this.accountForScaleDir.scale;
333
+
334
+ // AFTER
335
+ const config = this.autoScrollDir.autoScrollConfig();
336
+ const scale = this.accountForScaleDir.scale();
337
+ ```