pointrix 1.0.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/LICENSE +21 -0
- package/README.md +971 -0
- package/dist/pointrix-drag.cjs +1 -0
- package/dist/pointrix-drag.d.cts +189 -0
- package/dist/pointrix-drag.d.mts +189 -0
- package/dist/pointrix-drag.mjs +2 -0
- package/dist/pointrix-dropzone.cjs +1 -0
- package/dist/pointrix-dropzone.d.cts +99 -0
- package/dist/pointrix-dropzone.d.mts +99 -0
- package/dist/pointrix-dropzone.mjs +2 -0
- package/dist/pointrix-gesture.cjs +1 -0
- package/dist/pointrix-gesture.d.cts +119 -0
- package/dist/pointrix-gesture.d.mts +119 -0
- package/dist/pointrix-gesture.mjs +2 -0
- package/dist/pointrix-modifiers.cjs +1 -0
- package/dist/pointrix-modifiers.d.cts +334 -0
- package/dist/pointrix-modifiers.d.mts +334 -0
- package/dist/pointrix-modifiers.mjs +2 -0
- package/dist/pointrix-nano.cjs +1 -0
- package/dist/pointrix-nano.d.cts +82 -0
- package/dist/pointrix-nano.d.mts +82 -0
- package/dist/pointrix-nano.mjs +2 -0
- package/dist/pointrix-react.cjs +1 -0
- package/dist/pointrix-react.d.cts +537 -0
- package/dist/pointrix-react.d.mts +537 -0
- package/dist/pointrix-react.mjs +2 -0
- package/dist/pointrix-resize.cjs +1 -0
- package/dist/pointrix-resize.d.cts +193 -0
- package/dist/pointrix-resize.d.mts +193 -0
- package/dist/pointrix-resize.mjs +2 -0
- package/dist/pointrix-sortable.cjs +1 -0
- package/dist/pointrix-sortable.d.cts +89 -0
- package/dist/pointrix-sortable.d.mts +89 -0
- package/dist/pointrix-sortable.mjs +2 -0
- package/dist/pointrix-vue.cjs +1 -0
- package/dist/pointrix-vue.d.cts +485 -0
- package/dist/pointrix-vue.d.mts +485 -0
- package/dist/pointrix-vue.mjs +2 -0
- package/dist/pointrix.cjs +1 -0
- package/dist/pointrix.d.cts +784 -0
- package/dist/pointrix.d.mts +784 -0
- package/dist/pointrix.mjs +2 -0
- package/package.json +144 -0
package/README.md
ADDED
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
# Hyperact
|
|
2
|
+
|
|
3
|
+
Ultra-fast, zero-dependency drag/resize/gesture library for modern browsers. A high-performance, tree-shakeable alternative to interact.js.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Tiny footprint** -- core is 1.6 KB gzipped; full bundle under 10 KB gzipped
|
|
8
|
+
- **Zero runtime dependencies** -- nothing to audit, nothing to break
|
|
9
|
+
- **Modular architecture** -- import only drag, resize, gesture, dropzone, or sortable
|
|
10
|
+
- **Modifier pipeline** -- composable restrict, snap, inertia, magnetic snap, rubberband, and auto-scroll modifiers
|
|
11
|
+
- **Unified pointer events** -- mouse, touch, and pen handled identically
|
|
12
|
+
- **RAF-batched updates** -- single shared `requestAnimationFrame` loop across all instances
|
|
13
|
+
- **GPU-accelerated** -- uses `translate3d` for all transforms
|
|
14
|
+
- **Framework integrations** -- first-class React hooks/components and Vue 3 composables/directives
|
|
15
|
+
- **TypeScript-first** -- strict types for every option, event, and return value
|
|
16
|
+
- **Tree-shakeable** -- ES module sub-path exports; bundlers drop what you don't use
|
|
17
|
+
- **Event listener API** -- chainable `.on()` / `.off()` for all interaction events
|
|
18
|
+
- **Batch creation** -- `interactAll()` to create instances for many elements at once
|
|
19
|
+
- **Advanced filtering** -- `allowFrom` / `ignoreFrom` selectors, mouse button filtering, hold delay
|
|
20
|
+
- **Tap and gesture detection** -- built-in tap, double-tap, and hold callbacks
|
|
21
|
+
- **ARIA accessibility** -- automatic ARIA attributes for draggable, sortable, and dropzone elements with live screen reader announcements
|
|
22
|
+
- **i18n / Localization** -- all screen reader strings are customizable via `setMessages()` for any language
|
|
23
|
+
|
|
24
|
+
## Bundle Sizes
|
|
25
|
+
|
|
26
|
+
Measured from the `dist/` output (minified with terser, gzipped):
|
|
27
|
+
|
|
28
|
+
| Import path | Min | Gzip | What you get |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| `pointrix/nano` | 5.4 KB | 1.6 KB | Core pointer tracking, velocity, tap detection |
|
|
31
|
+
| `pointrix/drag` | 11.9 KB | 3.3 KB | Draggable with axis, bounds, grid, momentum, modifiers |
|
|
32
|
+
| `pointrix/resize` | 11.5 KB | 3.2 KB | Resizable with edges, aspect ratio, min/max, modifiers |
|
|
33
|
+
| `pointrix/gesture` | 8.3 KB | 2.3 KB | Multi-touch pinch, rotate, pan |
|
|
34
|
+
| `pointrix/dropzone` | 3.0 KB | 1.0 KB | Drop targets with overlap modes |
|
|
35
|
+
| `pointrix/sortable` | 18.2 KB | 5.0 KB | Sortable lists with cross-container group support |
|
|
36
|
+
| `pointrix/modifiers` | 8.3 KB | 2.6 KB | All modifiers (restrict, snapGrid, snapTargets, magneticSnap, inertia, autoScroll) |
|
|
37
|
+
| `pointrix` | 38.6 KB | 9.8 KB | Full bundle with everything |
|
|
38
|
+
|
|
39
|
+
For comparison, interact.js ships approximately 140 KB minified.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install pointrix
|
|
45
|
+
# or
|
|
46
|
+
pnpm add pointrix
|
|
47
|
+
# or
|
|
48
|
+
yarn add pointrix
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { draggable } from 'pointrix/drag'
|
|
55
|
+
|
|
56
|
+
const drag = draggable('#my-element', {
|
|
57
|
+
onDragMove: (e) => console.log(e.totalX, e.totalY),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Later:
|
|
61
|
+
drag.destroy()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Every factory function accepts an `HTMLElement` or a CSS selector string. Every instance has a `.destroy()` method and an `.enabled` property.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### Core Options (HyperactOptions)
|
|
71
|
+
|
|
72
|
+
All interaction types (draggable, resizable, gesturable) inherit these base options:
|
|
73
|
+
|
|
74
|
+
| Option | Type | Default | Description |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| `threshold` | `number` | `3` | Pixels of movement before interaction starts |
|
|
77
|
+
| `preventScroll` | `boolean` | `true` | Prevent touch scrolling while interacting |
|
|
78
|
+
| `holdDelay` | `number` | `0` | Hold delay in ms -- pointer must be held this long before the interaction starts |
|
|
79
|
+
| `mouseButtons` | `number` | `0` (any) | Bitmask of allowed mouse buttons: `1` = left, `2` = right, `4` = middle |
|
|
80
|
+
| `allowFrom` | `string` | -- | CSS selector -- only start if the pointer target matches |
|
|
81
|
+
| `ignoreFrom` | `string` | -- | CSS selector -- never start if the pointer target matches |
|
|
82
|
+
| `touchAction` | `string` | `'none'` | CSS `touch-action` value applied to the element |
|
|
83
|
+
| `styleCursor` | `boolean` | `true` | Whether to set cursor styles automatically |
|
|
84
|
+
| `enabled` | `boolean` | `true` | Enable or disable the instance (also available as a property) |
|
|
85
|
+
| `onTap` | `(event) => void` | -- | Called on tap (pointer down + up without exceeding threshold) |
|
|
86
|
+
| `onDoubleTap` | `(event) => void` | -- | Called on double-tap (two taps within 300 ms on the same target) |
|
|
87
|
+
| `onHold` | `(event) => void` | -- | Called when the pointer is held still for `holdDuration` ms without starting an interaction |
|
|
88
|
+
| `holdDuration` | `number` | `600` | Time in ms before `onHold` fires |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Event Listener API
|
|
93
|
+
|
|
94
|
+
Every instance (Hyperact, Draggable, Resizable, Gesturable) exposes chainable `.on()` and `.off()` methods for subscribing to events imperatively. This is an alternative to passing callbacks in the options object.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const drag = draggable('#el')
|
|
98
|
+
|
|
99
|
+
drag.on('dragstart', (e) => console.log('started'))
|
|
100
|
+
drag.on('dragmove', (e) => console.log(e.totalX, e.totalY))
|
|
101
|
+
drag.off('dragmove', handler)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
You can also read whether an interaction is currently in progress with the `.interacting` property:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
if (drag.interacting) {
|
|
108
|
+
console.log('drag is active')
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Events by class
|
|
113
|
+
|
|
114
|
+
**Hyperact (nano)**
|
|
115
|
+
|
|
116
|
+
| Event | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `start` | Interaction started (threshold exceeded or hold delay elapsed) |
|
|
119
|
+
| `move` | Pointer moved during an active interaction |
|
|
120
|
+
| `end` | Interaction ended |
|
|
121
|
+
| `tap` | Pointer released without exceeding threshold |
|
|
122
|
+
|
|
123
|
+
**Draggable**
|
|
124
|
+
|
|
125
|
+
| Event | Description |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `dragstart` | Drag started |
|
|
128
|
+
| `dragmove` | Pointer moved during drag |
|
|
129
|
+
| `dragend` | Drag ended |
|
|
130
|
+
|
|
131
|
+
**Resizable**
|
|
132
|
+
|
|
133
|
+
| Event | Description |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `resizestart` | Resize started |
|
|
136
|
+
| `resizemove` | Size changed during resize |
|
|
137
|
+
| `resizeend` | Resize ended |
|
|
138
|
+
|
|
139
|
+
**Gesturable**
|
|
140
|
+
|
|
141
|
+
| Event | Description |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `gesturestart` | Gesture started |
|
|
144
|
+
| `gesturemove` | Gesture updated |
|
|
145
|
+
| `gestureend` | Gesture ended |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Draggable
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { draggable } from 'pointrix/drag'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Options
|
|
156
|
+
|
|
157
|
+
| Option | Type | Default | Description |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| `axis` | `'x' \| 'y' \| 'xy' \| 'start'` | `'xy'` | Constrain movement to one axis. `'start'` auto-detects the axis from the initial movement direction |
|
|
160
|
+
| `startAxis` | `'x' \| 'y'` | -- | Only start the drag if the initial movement direction matches this axis |
|
|
161
|
+
| `handle` | `string \| HTMLElement` | -- | Only start drag when pointer is inside this element/selector |
|
|
162
|
+
| `bounds` | `'parent' \| HTMLElement \| {left?, top?, right?, bottom?}` | -- | Restrict movement within a region |
|
|
163
|
+
| `grid` | `{x: number, y: number}` | -- | Snap position to a grid |
|
|
164
|
+
| `momentum` | `boolean \| {friction?: number, minSpeed?: number}` | `false` | Physics-based momentum after release |
|
|
165
|
+
| `droppable` | `boolean` | `false` | Integrate with the `Dropzone` system |
|
|
166
|
+
| `modifiers` | `Modifier[]` | -- | Modifier chain applied each frame |
|
|
167
|
+
| `cursorChecker` | `(action: 'idle' \| 'grab' \| 'grabbing') => string` | -- | Custom function to determine the cursor for each drag state |
|
|
168
|
+
| `threshold` | `number` | `3` | Pixels of movement before drag starts |
|
|
169
|
+
| `preventScroll` | `boolean` | `true` | Prevent touch scrolling while dragging |
|
|
170
|
+
|
|
171
|
+
#### Events
|
|
172
|
+
|
|
173
|
+
| Callback | Event type | Key fields |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `onDragStart` | `DragEvent` | `dx`, `dy`, `totalX`, `totalY`, `velocityX`, `velocityY` |
|
|
176
|
+
| `onDragMove` | `DragEvent` | Same as above, updated each frame |
|
|
177
|
+
| `onDragEnd` | `DragEvent` | Final values |
|
|
178
|
+
|
|
179
|
+
#### Methods
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const d = draggable(el, options)
|
|
183
|
+
d.setPosition(100, 200) // Jump to a transform position
|
|
184
|
+
d.getPosition() // { x: number, y: number }
|
|
185
|
+
d.enabled = false // Disable (cancels active drag)
|
|
186
|
+
d.destroy() // Remove all listeners
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Example
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { draggable } from 'pointrix/drag'
|
|
193
|
+
import { snapGrid, inertia } from 'pointrix/modifiers'
|
|
194
|
+
|
|
195
|
+
draggable('#card', {
|
|
196
|
+
bounds: 'parent',
|
|
197
|
+
momentum: { friction: 0.92 },
|
|
198
|
+
modifiers: [
|
|
199
|
+
snapGrid({ x: 20, y: 20 }),
|
|
200
|
+
inertia({ resistance: 8 }),
|
|
201
|
+
],
|
|
202
|
+
onDragEnd: (e) => console.log('Dropped at', e.totalX, e.totalY),
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Example -- axis auto-detection
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
draggable('#swipeable', {
|
|
210
|
+
axis: 'start', // locks to x or y based on initial swipe direction
|
|
211
|
+
onDragEnd: (e) => console.log(e.totalX, e.totalY),
|
|
212
|
+
})
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### Resizable
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { resizable } from 'pointrix/resize'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Options
|
|
224
|
+
|
|
225
|
+
| Option | Type | Default | Description |
|
|
226
|
+
|---|---|---|---|
|
|
227
|
+
| `edges` | `{top?, right?, bottom?, left?}` (booleans) | All `true` | Which edges/corners can be dragged |
|
|
228
|
+
| `handleSize` | `number` | `10` | Pixel width of the resize handle area |
|
|
229
|
+
| `minWidth` | `number` | `50` | Minimum width in px |
|
|
230
|
+
| `minHeight` | `number` | `50` | Minimum height in px |
|
|
231
|
+
| `maxWidth` | `number` | `Infinity` | Maximum width in px |
|
|
232
|
+
| `maxHeight` | `number` | `Infinity` | Maximum height in px |
|
|
233
|
+
| `aspectRatio` | `number \| 'preserve'` | -- | Lock aspect ratio (number or preserve current) |
|
|
234
|
+
| `square` | `boolean` | `false` | Shorthand for `aspectRatio: 1` |
|
|
235
|
+
| `invert` | `'none' \| 'negate' \| 'reposition'` | `'none'` | How to handle resizing past the opposite edge. `'negate'` allows negative sizes; `'reposition'` flips the element |
|
|
236
|
+
| `grid` | `{width: number, height: number}` | -- | Snap size to a grid |
|
|
237
|
+
| `modifiers` | `Modifier[]` | -- | Modifier chain |
|
|
238
|
+
| `cursorChecker` | `(edge: string \| null) => string` | -- | Custom function to determine the cursor for each edge |
|
|
239
|
+
|
|
240
|
+
#### Events
|
|
241
|
+
|
|
242
|
+
| Callback | Event type | Key fields |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `onResizeStart` | `ResizeEvent` | `width`, `height`, `deltaWidth`, `deltaHeight`, `edges` |
|
|
245
|
+
| `onResizeMove` | `ResizeEvent` | Same, updated each frame |
|
|
246
|
+
| `onResizeEnd` | `ResizeEvent` | Final values |
|
|
247
|
+
|
|
248
|
+
#### Methods
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const r = resizable(el, options)
|
|
252
|
+
r.setSize(300, 200)
|
|
253
|
+
r.getSize() // { width: number, height: number }
|
|
254
|
+
r.enabled = false
|
|
255
|
+
r.destroy()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Example
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
resizable('#panel', {
|
|
262
|
+
edges: { right: true, bottom: true },
|
|
263
|
+
minWidth: 200,
|
|
264
|
+
aspectRatio: 16 / 9,
|
|
265
|
+
onResizeMove: (e) => console.log(`${e.width}x${e.height}`),
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Example -- invert mode
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
resizable('#box', {
|
|
273
|
+
invert: 'reposition', // allows dragging past opposite edge
|
|
274
|
+
square: true, // maintain 1:1 aspect ratio
|
|
275
|
+
onResizeMove: (e) => console.log(e.width, e.height),
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### Gesturable
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { gesturable } from 'pointrix/gesture'
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Multi-touch gesture recognition (pinch-to-zoom, rotate). Activates when the required number of pointers are down.
|
|
288
|
+
|
|
289
|
+
#### Options
|
|
290
|
+
|
|
291
|
+
| Option | Type | Default | Description |
|
|
292
|
+
|---|---|---|---|
|
|
293
|
+
| `minPointers` | `number` | `2` | Pointer count required to activate |
|
|
294
|
+
|
|
295
|
+
#### Events
|
|
296
|
+
|
|
297
|
+
| Callback | Event type | Key fields |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `onGestureStart` | `GestureEvent` | `scale`, `rotation`, `distance`, `angle`, `center`, `deltaScale`, `deltaAngle` |
|
|
300
|
+
| `onGestureMove` | `GestureEvent` | Same, updated each frame |
|
|
301
|
+
| `onGestureEnd` | `GestureEvent` | Final values |
|
|
302
|
+
|
|
303
|
+
#### Example
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { gesturable } from 'pointrix/gesture'
|
|
307
|
+
|
|
308
|
+
gesturable('#canvas', {
|
|
309
|
+
onGestureMove: (e) => {
|
|
310
|
+
applyZoom(e.scale)
|
|
311
|
+
applyRotation(e.rotation)
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### Dropzone
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { dropzone } from 'pointrix/dropzone'
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Define drop targets that respond to `draggable` elements created with `droppable: true`.
|
|
325
|
+
|
|
326
|
+
#### Options
|
|
327
|
+
|
|
328
|
+
| Option | Type | Default | Description |
|
|
329
|
+
|---|---|---|---|
|
|
330
|
+
| `accept` | `string \| (el: HTMLElement) => boolean` | -- | Filter which draggables can drop here |
|
|
331
|
+
| `overlap` | `'pointer' \| 'center' \| number` | `'pointer'` | How overlap is computed (`number` = area ratio threshold) |
|
|
332
|
+
| `activeClass` | `string` | -- | CSS class added while a compatible drag is in progress |
|
|
333
|
+
| `hoverClass` | `string` | -- | CSS class added while a draggable hovers over the zone |
|
|
334
|
+
|
|
335
|
+
#### Events
|
|
336
|
+
|
|
337
|
+
| Callback | Event type | Description |
|
|
338
|
+
|---|---|---|
|
|
339
|
+
| `onActivate` | `DropEvent` | A compatible drag started somewhere |
|
|
340
|
+
| `onDeactivate` | `DropEvent` | That drag ended |
|
|
341
|
+
| `onDragEnter` | `DropEvent` | Draggable entered this zone |
|
|
342
|
+
| `onDragLeave` | `DropEvent` | Draggable left this zone |
|
|
343
|
+
| `onDragOver` | `DropEvent` | Draggable is over this zone (fires each frame) |
|
|
344
|
+
| `onDrop` | `DropEvent` | Draggable was released over this zone |
|
|
345
|
+
|
|
346
|
+
`DropEvent` contains `target` (dropzone element), `draggable` (the dragged element), `overlap` (number), and `dragEvent`.
|
|
347
|
+
|
|
348
|
+
#### Example
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { draggable } from 'pointrix/drag'
|
|
352
|
+
import { dropzone } from 'pointrix/dropzone'
|
|
353
|
+
|
|
354
|
+
draggable('#item', { droppable: true })
|
|
355
|
+
|
|
356
|
+
dropzone('#bin', {
|
|
357
|
+
accept: '.deletable',
|
|
358
|
+
hoverClass: 'drop-hover',
|
|
359
|
+
onDrop: (e) => e.draggable.remove(),
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### Sortable
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { sortable } from 'pointrix/sortable'
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Drag-to-reorder lists with animated item displacement. Supports cross-container transfers via the `group` option.
|
|
372
|
+
|
|
373
|
+
#### Options
|
|
374
|
+
|
|
375
|
+
| Option | Type | Default | Description |
|
|
376
|
+
|---|---|---|---|
|
|
377
|
+
| `items` | `string` | direct children | CSS selector for sortable items |
|
|
378
|
+
| `axis` | `'x' \| 'y'` | `'y'` | Sort direction |
|
|
379
|
+
| `handle` | `string` | -- | CSS selector for drag handle within each item |
|
|
380
|
+
| `animationDuration` | `number` | `200` | Transition duration in ms for shifting items |
|
|
381
|
+
| `dragClass` | `string` | `'sortable-dragging'` | CSS class on the item being dragged |
|
|
382
|
+
| `hoverClass` | `string` | `'sortable-hover'` | CSS class on a container receiving a grouped item |
|
|
383
|
+
| `group` | `string` | -- | Group name; sortables sharing a group can exchange items |
|
|
384
|
+
|
|
385
|
+
#### Events
|
|
386
|
+
|
|
387
|
+
| Callback | Event type | Description |
|
|
388
|
+
|---|---|---|
|
|
389
|
+
| `onSort` | `SortEvent` | Order changed during drag (`item`, `oldIndex`, `newIndex`, `items`) |
|
|
390
|
+
| `onSortEnd` | `SortEvent` | Drag finished and DOM was reordered |
|
|
391
|
+
| `onAdd` | `SortTransferEvent` | An item was added from another sortable (`item`, `from`, `to`, `oldIndex`, `newIndex`) |
|
|
392
|
+
| `onRemove` | `SortTransferEvent` | An item was removed to another sortable |
|
|
393
|
+
|
|
394
|
+
#### Methods
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
const s = sortable('#list', options)
|
|
398
|
+
s.getOrder() // Current item elements in order
|
|
399
|
+
s.move(fromIndex, toIndex) // Programmatic reorder
|
|
400
|
+
s.refresh() // Re-scan items (after DOM changes)
|
|
401
|
+
s.enabled = false
|
|
402
|
+
s.destroy()
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
#### Example -- single list
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
sortable('#todo-list', {
|
|
409
|
+
handle: '.grip',
|
|
410
|
+
onSortEnd: (e) => saveOrder(e.items.map(el => el.dataset.id)),
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
#### Example -- cross-container groups
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
sortable('#backlog', { group: 'kanban', onRemove: (e) => console.log('removed', e.item) })
|
|
418
|
+
sortable('#in-progress', { group: 'kanban', onAdd: (e) => console.log('added', e.item) })
|
|
419
|
+
sortable('#done', { group: 'kanban' })
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### Modifiers
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
import {
|
|
428
|
+
restrict, snapGrid, snapTargets, magneticSnap, inertia, autoScroll,
|
|
429
|
+
rubberband, restrictSize, restrictEdges, snapSize, snapEdges,
|
|
430
|
+
} from 'pointrix/modifiers'
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Modifiers are composable transforms applied to the position each frame. Pass them as an array to the `modifiers` option of `draggable` or `resizable`.
|
|
434
|
+
|
|
435
|
+
#### `restrict(options)`
|
|
436
|
+
|
|
437
|
+
Clamp position within bounds.
|
|
438
|
+
|
|
439
|
+
| Option | Type | Description |
|
|
440
|
+
|---|---|---|
|
|
441
|
+
| `bounds` | `'parent' \| HTMLElement \| {left?, top?, right?, bottom?}` | Bounding region |
|
|
442
|
+
| `elementRect` | `{left, top, right, bottom}` (0-1 ratios) | Which part of the element must stay inside bounds |
|
|
443
|
+
| `endOnly` | `boolean` | Only apply restriction at drag end |
|
|
444
|
+
|
|
445
|
+
#### `snapGrid(options)`
|
|
446
|
+
|
|
447
|
+
Snap to a regular grid.
|
|
448
|
+
|
|
449
|
+
| Option | Type | Description |
|
|
450
|
+
|---|---|---|
|
|
451
|
+
| `x` | `number` | Grid cell width |
|
|
452
|
+
| `y` | `number` | Grid cell height |
|
|
453
|
+
| `offset` | `{x, y}` | Grid origin offset |
|
|
454
|
+
| `limits` | `{top?, left?, bottom?, right?}` | Clamp snapped position |
|
|
455
|
+
|
|
456
|
+
#### `snapTargets(options)`
|
|
457
|
+
|
|
458
|
+
Snap to arbitrary target positions with pivot support.
|
|
459
|
+
|
|
460
|
+
| Option | Type | Description |
|
|
461
|
+
|---|---|---|
|
|
462
|
+
| `targets` | `SnapTarget[]` | Array of `{x?, y?, range?}` |
|
|
463
|
+
| `range` | `number` (default `50`) | Default snap distance |
|
|
464
|
+
| `relativePoints` | `Array<PivotPreset \| {x, y}>` | Which point(s) on the element to test |
|
|
465
|
+
| `coordinateMode` | `'offset' \| 'parent'` | Target coordinate system |
|
|
466
|
+
|
|
467
|
+
**Pivot presets:** `'top-left'`, `'top'`, `'top-right'`, `'left'`, `'center'`, `'right'`, `'bottom-left'`, `'bottom'`, `'bottom-right'`
|
|
468
|
+
|
|
469
|
+
The returned modifier exposes `snappedTarget`, `snappedIndex`, and `isSnapped` for reading snap state.
|
|
470
|
+
|
|
471
|
+
#### `magneticSnap(options)`
|
|
472
|
+
|
|
473
|
+
Attract the element toward named targets with distance-based pull strength.
|
|
474
|
+
|
|
475
|
+
| Option | Type | Description |
|
|
476
|
+
|---|---|---|
|
|
477
|
+
| `targets` | `MagneticTarget[]` | Array of `{id, x, y, width?, height?, strength?}` |
|
|
478
|
+
| `distance` | `number` (default `30`) | Activation distance |
|
|
479
|
+
| `strength` | `number` (default `0.5`) | Pull strength (0-1) |
|
|
480
|
+
| `onSnap` | `(target) => void` | Called when element snaps to a target |
|
|
481
|
+
| `onUnsnap` | `(target) => void` | Called when element leaves a target |
|
|
482
|
+
|
|
483
|
+
Methods: `updateTargets(targets)`, `addTarget(target)`, `removeTarget(id)`, `getCurrentTarget()`, `isSnapped()`.
|
|
484
|
+
|
|
485
|
+
#### `inertia(options?)`
|
|
486
|
+
|
|
487
|
+
Continue movement after release using exponential decay.
|
|
488
|
+
|
|
489
|
+
| Option | Type | Default | Description |
|
|
490
|
+
|---|---|---|---|
|
|
491
|
+
| `resistance` | `number` | `10` | Decay constant (higher = more friction) |
|
|
492
|
+
| `minSpeed` | `number` | `10` | Speed below which inertia stops |
|
|
493
|
+
| `endSpeed` | `number` | `100` | Minimum release speed to trigger inertia |
|
|
494
|
+
| `smoothEnd` | `boolean` | `false` | Decelerate smoothly to current position |
|
|
495
|
+
| `smoothEndDuration` | `number` | `300` | Duration for smooth end (ms) |
|
|
496
|
+
|
|
497
|
+
#### `autoScroll(options?)`
|
|
498
|
+
|
|
499
|
+
Scroll a container when the pointer approaches its edges.
|
|
500
|
+
|
|
501
|
+
| Option | Type | Default | Description |
|
|
502
|
+
|---|---|---|---|
|
|
503
|
+
| `container` | `HTMLElement \| Window` | auto-detected | Scroll container |
|
|
504
|
+
| `speed` | `number` | `10` | Scroll speed (px/frame) |
|
|
505
|
+
| `margin` | `number` | `50` | Edge proximity threshold (px) |
|
|
506
|
+
| `acceleration` | `number` | `5` | Acceleration multiplier |
|
|
507
|
+
|
|
508
|
+
#### `rubberband(options)`
|
|
509
|
+
|
|
510
|
+
Allow the element to be dragged past bounds with elastic resistance, then snap back on release.
|
|
511
|
+
|
|
512
|
+
| Option | Type | Default | Description |
|
|
513
|
+
|---|---|---|---|
|
|
514
|
+
| `bounds` | `'parent' \| {left?, top?, right?, bottom?}` | -- | Bounding region |
|
|
515
|
+
| `resistance` | `number` | `0.15` | Resistance factor 0-1. Lower = more resistance |
|
|
516
|
+
| `maxOvershoot` | `number` | `100` | Maximum overshoot in pixels |
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
draggable('#el', {
|
|
520
|
+
modifiers: [
|
|
521
|
+
rubberband({ bounds: 'parent', resistance: 0.2 }),
|
|
522
|
+
],
|
|
523
|
+
})
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### `restrictSize(options)`
|
|
527
|
+
|
|
528
|
+
Clamp the element's size during a resize interaction.
|
|
529
|
+
|
|
530
|
+
| Option | Type | Description |
|
|
531
|
+
|---|---|---|
|
|
532
|
+
| `min` | `{width?: number, height?: number}` | Minimum size |
|
|
533
|
+
| `max` | `{width?: number, height?: number}` | Maximum size |
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
resizable('#panel', {
|
|
537
|
+
modifiers: [
|
|
538
|
+
restrictSize({ min: { width: 100, height: 100 }, max: { width: 800, height: 600 } }),
|
|
539
|
+
],
|
|
540
|
+
})
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
#### `restrictEdges(options)`
|
|
544
|
+
|
|
545
|
+
Restrict individual edge positions during a resize interaction.
|
|
546
|
+
|
|
547
|
+
| Option | Type | Description |
|
|
548
|
+
|---|---|---|
|
|
549
|
+
| `outer` | `{left?, top?, right?, bottom?}` | Edges cannot go beyond these values (outward limit) |
|
|
550
|
+
| `inner` | `{left?, top?, right?, bottom?}` | Edges cannot pass these values toward the center (inward limit) |
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
resizable('#panel', {
|
|
554
|
+
modifiers: [
|
|
555
|
+
restrictEdges({
|
|
556
|
+
outer: { left: 0, top: 0, right: 800, bottom: 600 },
|
|
557
|
+
inner: { left: 100, top: 100 },
|
|
558
|
+
}),
|
|
559
|
+
],
|
|
560
|
+
})
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
#### `snapSize(options)`
|
|
564
|
+
|
|
565
|
+
Snap the element's width and height to a grid during resize.
|
|
566
|
+
|
|
567
|
+
| Option | Type | Description |
|
|
568
|
+
|---|---|---|
|
|
569
|
+
| `width` | `number` | Grid cell width for snapping |
|
|
570
|
+
| `height` | `number` | Grid cell height for snapping |
|
|
571
|
+
| `offset` | `{width?: number, height?: number}` | Grid origin offset |
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
resizable('#panel', {
|
|
575
|
+
modifiers: [
|
|
576
|
+
snapSize({ width: 50, height: 50 }),
|
|
577
|
+
],
|
|
578
|
+
})
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### `snapEdges(options)`
|
|
582
|
+
|
|
583
|
+
Snap individual edges to target positions during resize.
|
|
584
|
+
|
|
585
|
+
| Option | Type | Description |
|
|
586
|
+
|---|---|---|
|
|
587
|
+
| `targets` | `SnapEdgeTarget[]` | Array of `{left?, top?, right?, bottom?, range?}` |
|
|
588
|
+
| `range` | `number` (default `20`) | Default snap distance |
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
resizable('#panel', {
|
|
592
|
+
modifiers: [
|
|
593
|
+
snapEdges({
|
|
594
|
+
targets: [{ left: 0, top: 0, right: 800, bottom: 600 }],
|
|
595
|
+
range: 30,
|
|
596
|
+
}),
|
|
597
|
+
],
|
|
598
|
+
})
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
#### Composing modifiers
|
|
602
|
+
|
|
603
|
+
```ts
|
|
604
|
+
draggable('#el', {
|
|
605
|
+
modifiers: [
|
|
606
|
+
restrict({ bounds: 'parent' }),
|
|
607
|
+
snapGrid({ x: 25, y: 25 }),
|
|
608
|
+
inertia({ resistance: 12 }),
|
|
609
|
+
autoScroll({ margin: 60 }),
|
|
610
|
+
],
|
|
611
|
+
})
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Modifiers run in array order. Each modifier receives the output of the previous one.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
### Interactable
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
import { interactable } from 'pointrix'
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Convenience factory that creates drag, resize, and gesture instances on the same element. Hyperact coordinates them automatically (resize has priority over drag when pointer is near an edge).
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
const ia = interactable('#widget', {
|
|
628
|
+
drag: { momentum: true },
|
|
629
|
+
resize: { edges: { right: true, bottom: true }, minWidth: 120 },
|
|
630
|
+
gesture: true,
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
ia.drag // Draggable | null
|
|
634
|
+
ia.resize // Resizable | null
|
|
635
|
+
ia.gesture // Gesturable | null
|
|
636
|
+
ia.destroy()
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Pass `true` for default options or an options object.
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
### interactAll()
|
|
644
|
+
|
|
645
|
+
```ts
|
|
646
|
+
import { interactAll } from 'pointrix'
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Create interaction instances for every element matching a CSS selector. Returns an object with an `instances` array and a single `destroy()` method to tear down all of them.
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
const result = interactAll('.card', { drag: true, resize: true })
|
|
653
|
+
|
|
654
|
+
result.instances // Array of interactable results
|
|
655
|
+
result.destroy() // Destroys all instances at once
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
### Enabling and Disabling
|
|
661
|
+
|
|
662
|
+
Every instance has an `enabled` property. Setting it to `false` cancels any active interaction and ignores future pointer events. Setting it back to `true` re-enables the instance.
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
const drag = draggable('#el', { onDragMove: (e) => console.log(e.totalX) })
|
|
666
|
+
|
|
667
|
+
// Disable
|
|
668
|
+
drag.enabled = false
|
|
669
|
+
|
|
670
|
+
// Re-enable
|
|
671
|
+
drag.enabled = true
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
You can also check whether an interaction is in progress:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
drag.interacting // true while a drag is active
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Accessibility (ARIA)
|
|
683
|
+
|
|
684
|
+
Hyperact automatically applies ARIA attributes and provides live screen reader announcements for draggable, sortable, and dropzone interactions. A visually hidden live region (`aria-live="assertive"`) and a shared instructions element are created lazily in the DOM when needed.
|
|
685
|
+
|
|
686
|
+
All ARIA behavior is enabled by default. No configuration is required for basic accessibility support.
|
|
687
|
+
|
|
688
|
+
### Draggable ARIA
|
|
689
|
+
|
|
690
|
+
When a `draggable` is created, the following attributes are set on the element:
|
|
691
|
+
|
|
692
|
+
| Attribute | Value | Purpose |
|
|
693
|
+
|---|---|---|
|
|
694
|
+
| `tabindex` | `0` | Makes the element keyboard-focusable (only set if not already present) |
|
|
695
|
+
| `role` | `button` | Identifies the element as an interactive control (only set if not already present) |
|
|
696
|
+
| `aria-roledescription` | `draggable` | Tells screen readers this is a draggable element |
|
|
697
|
+
| `aria-describedby` | `grip-instructions` | Points to a visually hidden element containing keyboard instructions |
|
|
698
|
+
| `aria-grabbed` | `true` / `false` | Toggled when a drag starts and ends |
|
|
699
|
+
|
|
700
|
+
Screen readers will announce the element as a "draggable button" and read the keyboard instructions on focus. When a drag starts, the live region announces "Picked up"; when it ends, "Dropped".
|
|
701
|
+
|
|
702
|
+
### Sortable ARIA
|
|
703
|
+
|
|
704
|
+
Sortable containers and their items receive additional attributes to convey list semantics and position:
|
|
705
|
+
|
|
706
|
+
**Container:**
|
|
707
|
+
|
|
708
|
+
| Attribute | Value | Purpose |
|
|
709
|
+
|---|---|---|
|
|
710
|
+
| `role` | `listbox` | Identifies the container as an ordered list (only set if not already present) |
|
|
711
|
+
|
|
712
|
+
**Items:**
|
|
713
|
+
|
|
714
|
+
| Attribute | Value | Purpose |
|
|
715
|
+
|---|---|---|
|
|
716
|
+
| `tabindex` | `0` | Keyboard-focusable |
|
|
717
|
+
| `role` | `option` | Identifies each item as a list option (only set if not already present) |
|
|
718
|
+
| `aria-roledescription` | `sortable` | Tells screen readers this is a sortable item |
|
|
719
|
+
| `aria-describedby` | `grip-instructions` | Keyboard instructions |
|
|
720
|
+
| `aria-posinset` | `1`, `2`, ... | Current position in the list (1-based) |
|
|
721
|
+
| `aria-setsize` | total count | Total number of items in the list |
|
|
722
|
+
|
|
723
|
+
During a sort operation, the live region announces position changes:
|
|
724
|
+
|
|
725
|
+
- **Pick up:** "Picked up [label], position 3 of 10"
|
|
726
|
+
- **Move:** "Moved to position 5 of 10"
|
|
727
|
+
- **Drop:** "Dropped [label] in position 5 of 10"
|
|
728
|
+
|
|
729
|
+
### Dropzone ARIA
|
|
730
|
+
|
|
731
|
+
Dropzone elements receive `aria-dropeffect` to communicate their state to assistive technology:
|
|
732
|
+
|
|
733
|
+
| State | `aria-dropeffect` value |
|
|
734
|
+
|---|---|
|
|
735
|
+
| Default (initialized) | `move` |
|
|
736
|
+
| Active (compatible drag in progress) | `move` |
|
|
737
|
+
| Inactive (no compatible drag) | `none` |
|
|
738
|
+
|
|
739
|
+
The attribute is toggled automatically when a compatible draggable starts or ends.
|
|
740
|
+
|
|
741
|
+
### Opting Out
|
|
742
|
+
|
|
743
|
+
Pass `aria: false` to any factory function to disable all ARIA attribute management for that instance:
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
draggable('#el', { aria: false })
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### i18n / Localization
|
|
750
|
+
|
|
751
|
+
All screen reader announcement strings can be customized using `setMessages()`. This allows full translation of every ARIA string Hyperact produces.
|
|
752
|
+
|
|
753
|
+
#### The `AriaMessages` interface
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
interface AriaMessages {
|
|
757
|
+
instructions: string
|
|
758
|
+
pickedUp: (label: string, position: number, total: number) => string
|
|
759
|
+
movedTo: (position: number, total: number) => string
|
|
760
|
+
dropped: (label: string, position: number, total: number) => string
|
|
761
|
+
dragPickedUp: string
|
|
762
|
+
dragDropped: string
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
#### Default values
|
|
767
|
+
|
|
768
|
+
| Key | Default |
|
|
769
|
+
|---|---|
|
|
770
|
+
| `instructions` | `'Press Space or Enter to pick up. Use arrow keys to move. Press Space or Enter to drop. Press Escape to cancel.'` |
|
|
771
|
+
| `pickedUp` | `` (label, pos, total) => `Picked up ${label}, position ${pos} of ${total}` `` |
|
|
772
|
+
| `movedTo` | `` (pos, total) => `Moved to position ${pos} of ${total}` `` |
|
|
773
|
+
| `dropped` | `` (label, pos, total) => `Dropped ${label} in position ${pos} of ${total}` `` |
|
|
774
|
+
| `dragPickedUp` | `'Picked up'` |
|
|
775
|
+
| `dragDropped` | `'Dropped'` |
|
|
776
|
+
|
|
777
|
+
#### Overriding messages
|
|
778
|
+
|
|
779
|
+
Use `setMessages()` with a partial or full set of replacements. Any keys you omit will keep their current values.
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
import { setMessages } from 'pointrix'
|
|
783
|
+
|
|
784
|
+
setMessages({
|
|
785
|
+
instructions: '...',
|
|
786
|
+
pickedUp: (label, pos, total) => `...`,
|
|
787
|
+
movedTo: (pos, total) => `...`,
|
|
788
|
+
dropped: (label, pos, total) => `...`,
|
|
789
|
+
dragPickedUp: '...',
|
|
790
|
+
dragDropped: '...',
|
|
791
|
+
})
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
#### Full translation example (German)
|
|
795
|
+
|
|
796
|
+
```ts
|
|
797
|
+
import { setMessages } from 'pointrix'
|
|
798
|
+
|
|
799
|
+
setMessages({
|
|
800
|
+
instructions:
|
|
801
|
+
'Leertaste oder Eingabetaste drücken zum Aufnehmen. Pfeiltasten zum Verschieben. Leertaste oder Eingabetaste zum Ablegen. Escape zum Abbrechen.',
|
|
802
|
+
pickedUp: (label, pos, total) => `${label} aufgenommen, Position ${pos} von ${total}`,
|
|
803
|
+
movedTo: (pos, total) => `Verschoben auf Position ${pos} von ${total}`,
|
|
804
|
+
dropped: (label, pos, total) => `${label} abgelegt auf Position ${pos} von ${total}`,
|
|
805
|
+
dragPickedUp: 'Aufgenommen',
|
|
806
|
+
dragDropped: 'Abgelegt',
|
|
807
|
+
})
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
You can also read the current messages at any time with `getMessages()`:
|
|
811
|
+
|
|
812
|
+
```ts
|
|
813
|
+
import { getMessages } from 'pointrix'
|
|
814
|
+
|
|
815
|
+
const current = getMessages()
|
|
816
|
+
console.log(current.instructions)
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## React Integration
|
|
822
|
+
|
|
823
|
+
```ts
|
|
824
|
+
import { useDraggable, useResizable, useGesturable, useDropzone, useSortable, useInteractable } from 'pointrix/react'
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Each hook returns `{ ref, instance }`. Attach `ref` to your element; read or control the interaction through `instance.current`.
|
|
828
|
+
|
|
829
|
+
### Hooks
|
|
830
|
+
|
|
831
|
+
```tsx
|
|
832
|
+
function DraggableCard() {
|
|
833
|
+
const { ref } = useDraggable({
|
|
834
|
+
bounds: 'parent',
|
|
835
|
+
onDragEnd: (e) => console.log(e.totalX, e.totalY),
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
return <div ref={ref}>Drag me</div>
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Pass a dependency array as the second argument to recreate the instance when values change:
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
const { ref } = useDraggable({ axis }, [axis])
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Components
|
|
849
|
+
|
|
850
|
+
Pre-built components that forward options as props:
|
|
851
|
+
|
|
852
|
+
```tsx
|
|
853
|
+
import { DraggableComponent, ResizableComponent, InteractableComponent } from 'pointrix/react'
|
|
854
|
+
|
|
855
|
+
<DraggableComponent bounds="parent" as="section" className="card">
|
|
856
|
+
Content
|
|
857
|
+
</DraggableComponent>
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
The `as` prop controls the rendered element (default `div`).
|
|
861
|
+
|
|
862
|
+
### Available hooks and components
|
|
863
|
+
|
|
864
|
+
| Hook | Component | Import |
|
|
865
|
+
|---|---|---|
|
|
866
|
+
| `useGrip` | `GripComponent` | `pointrix/react` |
|
|
867
|
+
| `useDraggable` | `DraggableComponent` | `pointrix/react` |
|
|
868
|
+
| `useResizable` | `ResizableComponent` | `pointrix/react` |
|
|
869
|
+
| `useGesturable` | `GesturableComponent` | `pointrix/react` |
|
|
870
|
+
| `useDropzone` | -- | `pointrix/react` |
|
|
871
|
+
| `useSortable` | -- | `pointrix/react` |
|
|
872
|
+
| `useInteractable` | `InteractableComponent` | `pointrix/react` |
|
|
873
|
+
|
|
874
|
+
---
|
|
875
|
+
|
|
876
|
+
## Vue 3 Integration
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
import { useDraggable, vDraggable, GripPlugin } from 'pointrix/vue'
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### Composables
|
|
883
|
+
|
|
884
|
+
Each composable returns `{ elRef, instance }`. Bind `elRef` with `ref=` in your template.
|
|
885
|
+
|
|
886
|
+
```vue
|
|
887
|
+
<script setup lang="ts">
|
|
888
|
+
import { useDraggable } from 'pointrix/vue'
|
|
889
|
+
|
|
890
|
+
const { elRef } = useDraggable({
|
|
891
|
+
bounds: 'parent',
|
|
892
|
+
onDragEnd: (e) => console.log(e.totalX, e.totalY),
|
|
893
|
+
})
|
|
894
|
+
</script>
|
|
895
|
+
|
|
896
|
+
<template>
|
|
897
|
+
<div :ref="elRef">Drag me</div>
|
|
898
|
+
</template>
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
Pass a `ref()` as options to automatically recreate the instance when options change:
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
const opts = ref<DragOptions>({ axis: 'x' })
|
|
905
|
+
const { elRef } = useDraggable(opts)
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
### Directives
|
|
909
|
+
|
|
910
|
+
```vue
|
|
911
|
+
<template>
|
|
912
|
+
<div v-draggable="{ bounds: 'parent' }">Drag me</div>
|
|
913
|
+
<div v-resizable="{ minWidth: 100 }">Resize me</div>
|
|
914
|
+
<div v-gesturable>Pinch me</div>
|
|
915
|
+
<div v-sortable="{ axis: 'y' }">Sort me</div>
|
|
916
|
+
</template>
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Plugin
|
|
920
|
+
|
|
921
|
+
Register all directives globally:
|
|
922
|
+
|
|
923
|
+
```ts
|
|
924
|
+
import { createApp } from 'vue'
|
|
925
|
+
import { GripPlugin } from 'pointrix/vue'
|
|
926
|
+
|
|
927
|
+
createApp(App).use(GripPlugin).mount('#app')
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
This registers `v-draggable`, `v-resizable`, `v-gesturable`, and `v-sortable`.
|
|
931
|
+
|
|
932
|
+
### Available composables and directives
|
|
933
|
+
|
|
934
|
+
| Composable | Directive |
|
|
935
|
+
|---|---|
|
|
936
|
+
| `useGrip` | -- |
|
|
937
|
+
| `useDraggable` | `vDraggable` |
|
|
938
|
+
| `useResizable` | `vResizable` |
|
|
939
|
+
| `useGesturable` | `vGesturable` |
|
|
940
|
+
| `useDropzone` | -- |
|
|
941
|
+
| `useSortable` | `vSortable` |
|
|
942
|
+
| `useInteractable` | -- |
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Tree-Shaking and Sub-Path Imports
|
|
947
|
+
|
|
948
|
+
Every module is available as a separate entry point. Your bundler will only include the code you actually import.
|
|
949
|
+
|
|
950
|
+
```ts
|
|
951
|
+
// Only drag -- pulls in nano as a dependency (~3.3 KB gzip)
|
|
952
|
+
import { draggable } from 'pointrix/drag'
|
|
953
|
+
|
|
954
|
+
// Only modifiers
|
|
955
|
+
import { snapGrid, inertia } from 'pointrix/modifiers'
|
|
956
|
+
|
|
957
|
+
// Only React hooks
|
|
958
|
+
import { useDraggable } from 'pointrix/react'
|
|
959
|
+
|
|
960
|
+
// Only Vue composables
|
|
961
|
+
import { useDraggable } from 'pointrix/vue'
|
|
962
|
+
|
|
963
|
+
// Full bundle if you need everything
|
|
964
|
+
import { interactable, interactAll, draggable, resizable, gesturable, dropzone, sortable } from 'pointrix'
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
All entry points ship ESM (`.mjs`) and CJS (`.cjs`) with full TypeScript declarations.
|
|
968
|
+
|
|
969
|
+
## License
|
|
970
|
+
|
|
971
|
+
MIT
|