solid-drift 0.19.0 → 0.20.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/README.md +26 -0
- package/dist/gesture.d.ts +82 -0
- package/dist/gesture.js +107 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -412,6 +412,32 @@ const { x, y, status } = createDrag(() => card, {
|
|
|
412
412
|
|
|
413
413
|
Returns `{ x, y, status }`: the drag offset in pixels and `status` (`"idle"`, `"dragging"`, `"settling"`). SSR-safe: everything rests at 0. Under reduced motion the drag still tracks the pointer (direct manipulation is not animation) but release snaps instantly to the constrained target with no glide.
|
|
414
414
|
|
|
415
|
+
### `createSwipe(ref, options?)`
|
|
416
|
+
|
|
417
|
+
Touch swipe gesture recognition: swipe-to-dismiss, carousels. While `createDrag` tracks the pointer continuously, `createSwipe` makes the discrete decision: was that gesture a swipe, and which way? On pointerup it compares travel, duration, and velocity against the thresholds and fires the matching callbacks plus the `lastSwipe` signal. Pointer Events give touch parity for free: mouse, touch, and pen run through the same path. For touch, set `touch-action: pan-y` on a horizontal swipe surface (or `pan-x` for vertical) so the browser does not hijack the gesture; use `none` when recognizing both axes.
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
import { createSwipe } from "solid-drift"
|
|
421
|
+
|
|
422
|
+
let deck!: HTMLDivElement
|
|
423
|
+
const { lastSwipe } = createSwipe(() => deck, {
|
|
424
|
+
onSwipeLeft: () => dismiss(),
|
|
425
|
+
onSwipeRight: () => keep(),
|
|
426
|
+
})
|
|
427
|
+
<div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
| Option | Default | Description |
|
|
431
|
+
| -------------- | ------- | ------------------------------------------------------------------------ |
|
|
432
|
+
| `threshold` | `48` | Minimum travel in px to count as a swipe |
|
|
433
|
+
| `maxDuration` | `800` | Maximum gesture duration in ms |
|
|
434
|
+
| `minVelocity` | `0.4` | Minimum velocity in px/ms; a fast flick below `threshold` still counts |
|
|
435
|
+
| `axis` | `"both"`| `"x"`, `"y"`, or `"both"` |
|
|
436
|
+
| `onSwipe` | none | Called for every recognized swipe with `{ direction, distance, velocity, duration, from, to }` |
|
|
437
|
+
| `onSwipeLeft` / `onSwipeRight` / `onSwipeUp` / `onSwipeDown` | none | Per-direction callbacks |
|
|
438
|
+
|
|
439
|
+
Returns `{ lastSwipe, reset }`. A swipe counts when travel passes `threshold` inside `maxDuration`, or velocity passes `minVelocity`; slow long drags are not swipes. Recognition is not animation, so it works identically under reduced motion; the host decides how to animate the response. SSR-safe: `lastSwipe()` stays null and callbacks never fire.
|
|
440
|
+
|
|
415
441
|
### `createTrail(source, options?)`
|
|
416
442
|
|
|
417
443
|
A signal that replays another signal's past: it returns the value the source had `delay` milliseconds ago, interpolated between samples. Chain trails off one source for follower effects (a cursor with a comet tail, cascading highlights), or trail a scroll progress for a delayed echo of the page. The trail catches up and parks exactly on the latest value when the source rests. The follow loop runs only while the trail is behind.
|
package/dist/gesture.d.ts
CHANGED
|
@@ -105,4 +105,86 @@ export interface DragControls {
|
|
|
105
105
|
* ```
|
|
106
106
|
*/
|
|
107
107
|
export declare function createDrag(ref: MaybeElement, options?: DragOptions): DragControls;
|
|
108
|
+
/** Cardinal direction of a recognized swipe. */
|
|
109
|
+
export type SwipeDirection = "left" | "right" | "up" | "down";
|
|
110
|
+
/** Snapshot delivered for a recognized swipe. */
|
|
111
|
+
export interface SwipeDetails {
|
|
112
|
+
direction: SwipeDirection;
|
|
113
|
+
/** Primary-axis travel in px. Always positive. */
|
|
114
|
+
distance: number;
|
|
115
|
+
/** Primary-axis velocity in px/ms. Always positive. */
|
|
116
|
+
velocity: number;
|
|
117
|
+
/** Gesture duration in ms. */
|
|
118
|
+
duration: number;
|
|
119
|
+
/** Pointer path endpoints in client pixels. */
|
|
120
|
+
from: {
|
|
121
|
+
x: number;
|
|
122
|
+
y: number;
|
|
123
|
+
};
|
|
124
|
+
to: {
|
|
125
|
+
x: number;
|
|
126
|
+
y: number;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export interface SwipeOptions {
|
|
130
|
+
/** Minimum travel in px to count as a swipe. Default 48. */
|
|
131
|
+
threshold?: number;
|
|
132
|
+
/** Maximum gesture duration in ms. Default 800. */
|
|
133
|
+
maxDuration?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Minimum primary-axis velocity in px/ms. A fast flick shorter than
|
|
136
|
+
* `threshold` still counts as a swipe. Default 0.4.
|
|
137
|
+
*/
|
|
138
|
+
minVelocity?: number;
|
|
139
|
+
/** Lock recognition to an axis. Default "both". */
|
|
140
|
+
axis?: DragAxis;
|
|
141
|
+
/** Called for every recognized swipe. */
|
|
142
|
+
onSwipe?: (details: SwipeDetails) => void;
|
|
143
|
+
/** Called for a leftward swipe. */
|
|
144
|
+
onSwipeLeft?: (details: SwipeDetails) => void;
|
|
145
|
+
/** Called for a rightward swipe. */
|
|
146
|
+
onSwipeRight?: (details: SwipeDetails) => void;
|
|
147
|
+
/** Called for an upward swipe. */
|
|
148
|
+
onSwipeUp?: (details: SwipeDetails) => void;
|
|
149
|
+
/** Called for a downward swipe. */
|
|
150
|
+
onSwipeDown?: (details: SwipeDetails) => void;
|
|
151
|
+
}
|
|
152
|
+
export interface SwipeControls {
|
|
153
|
+
/** The most recent recognized swipe, or null if none yet. */
|
|
154
|
+
lastSwipe: Accessor<SwipeDetails | null>;
|
|
155
|
+
/** Clear the last swipe. */
|
|
156
|
+
reset: () => void;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Touch swipe gesture recognition: swipe-to-dismiss, carousels.
|
|
160
|
+
*
|
|
161
|
+
* While `createDrag` tracks the pointer continuously, `createSwipe`
|
|
162
|
+
* makes the discrete decision: was that gesture a swipe, and which
|
|
163
|
+
* way? On pointerup it compares travel, duration, and velocity
|
|
164
|
+
* against the thresholds and fires the matching callbacks plus the
|
|
165
|
+
* `lastSwipe` signal.
|
|
166
|
+
*
|
|
167
|
+
* Pointer Events give touch parity for free: mouse, touch, and pen
|
|
168
|
+
* run through the same path. For touch, set `touch-action: pan-y` on
|
|
169
|
+
* a horizontal swipe surface (or `pan-x` for vertical) so the browser
|
|
170
|
+
* does not hijack the gesture; use `none` when recognizing both axes.
|
|
171
|
+
*
|
|
172
|
+
* A swipe counts when travel passes `threshold` inside `maxDuration`,
|
|
173
|
+
* or when velocity passes `minVelocity` (a fast flick). Slow long
|
|
174
|
+
* drags are not swipes; they belong to `createDrag`.
|
|
175
|
+
*
|
|
176
|
+
* Recognition is not animation, so it works identically under
|
|
177
|
+
* reduced motion; the host decides how to animate the response.
|
|
178
|
+
* SSR-safe: `lastSwipe()` stays null and callbacks never fire.
|
|
179
|
+
*
|
|
180
|
+
* ```tsx
|
|
181
|
+
* let deck!: HTMLDivElement
|
|
182
|
+
* const { lastSwipe } = createSwipe(() => deck, {
|
|
183
|
+
* onSwipeLeft: () => dismiss(),
|
|
184
|
+
* onSwipeRight: () => keep(),
|
|
185
|
+
* })
|
|
186
|
+
* <div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export declare function createSwipe(ref: MaybeElement, options?: SwipeOptions): SwipeControls;
|
|
108
190
|
export {};
|
package/dist/gesture.js
CHANGED
|
@@ -205,3 +205,110 @@ export function createDrag(ref, options = {}) {
|
|
|
205
205
|
onCleanup(() => stopSettle());
|
|
206
206
|
return { x, y, status };
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Touch swipe gesture recognition: swipe-to-dismiss, carousels.
|
|
210
|
+
*
|
|
211
|
+
* While `createDrag` tracks the pointer continuously, `createSwipe`
|
|
212
|
+
* makes the discrete decision: was that gesture a swipe, and which
|
|
213
|
+
* way? On pointerup it compares travel, duration, and velocity
|
|
214
|
+
* against the thresholds and fires the matching callbacks plus the
|
|
215
|
+
* `lastSwipe` signal.
|
|
216
|
+
*
|
|
217
|
+
* Pointer Events give touch parity for free: mouse, touch, and pen
|
|
218
|
+
* run through the same path. For touch, set `touch-action: pan-y` on
|
|
219
|
+
* a horizontal swipe surface (or `pan-x` for vertical) so the browser
|
|
220
|
+
* does not hijack the gesture; use `none` when recognizing both axes.
|
|
221
|
+
*
|
|
222
|
+
* A swipe counts when travel passes `threshold` inside `maxDuration`,
|
|
223
|
+
* or when velocity passes `minVelocity` (a fast flick). Slow long
|
|
224
|
+
* drags are not swipes; they belong to `createDrag`.
|
|
225
|
+
*
|
|
226
|
+
* Recognition is not animation, so it works identically under
|
|
227
|
+
* reduced motion; the host decides how to animate the response.
|
|
228
|
+
* SSR-safe: `lastSwipe()` stays null and callbacks never fire.
|
|
229
|
+
*
|
|
230
|
+
* ```tsx
|
|
231
|
+
* let deck!: HTMLDivElement
|
|
232
|
+
* const { lastSwipe } = createSwipe(() => deck, {
|
|
233
|
+
* onSwipeLeft: () => dismiss(),
|
|
234
|
+
* onSwipeRight: () => keep(),
|
|
235
|
+
* })
|
|
236
|
+
* <div ref={deck} style={{ "touch-action": "pan-y" }}>card</div>
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export function createSwipe(ref, options = {}) {
|
|
240
|
+
const none = () => null;
|
|
241
|
+
if (typeof window === "undefined") {
|
|
242
|
+
return { lastSwipe: none, reset: () => { } };
|
|
243
|
+
}
|
|
244
|
+
const { threshold = 48, maxDuration = 800, minVelocity = 0.4, axis = "both", onSwipe, onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, } = options;
|
|
245
|
+
const [lastSwipe, setLastSwipe] = createSignal(null);
|
|
246
|
+
const fire = (details) => {
|
|
247
|
+
setLastSwipe(details);
|
|
248
|
+
onSwipe?.(details);
|
|
249
|
+
if (details.direction === "left")
|
|
250
|
+
onSwipeLeft?.(details);
|
|
251
|
+
else if (details.direction === "right")
|
|
252
|
+
onSwipeRight?.(details);
|
|
253
|
+
else if (details.direction === "up")
|
|
254
|
+
onSwipeUp?.(details);
|
|
255
|
+
else
|
|
256
|
+
onSwipeDown?.(details);
|
|
257
|
+
};
|
|
258
|
+
const onDown = (event) => {
|
|
259
|
+
if (event.isPrimary === false)
|
|
260
|
+
return;
|
|
261
|
+
const el = ref();
|
|
262
|
+
if (!el)
|
|
263
|
+
return;
|
|
264
|
+
const startX = event.clientX;
|
|
265
|
+
const startY = event.clientY;
|
|
266
|
+
const startT = now();
|
|
267
|
+
const up = (ev) => {
|
|
268
|
+
window.removeEventListener("pointerup", up);
|
|
269
|
+
window.removeEventListener("pointercancel", cancel);
|
|
270
|
+
const dx = ev.clientX - startX;
|
|
271
|
+
const dy = ev.clientY - startY;
|
|
272
|
+
const dt = Math.max(now() - startT, 1);
|
|
273
|
+
// Primary axis, honoring the axis lock.
|
|
274
|
+
const horizontal = axis === "x" || (axis === "both" && Math.abs(dx) >= Math.abs(dy));
|
|
275
|
+
const travel = horizontal ? dx : dy;
|
|
276
|
+
const distance = Math.abs(travel);
|
|
277
|
+
const velocity = distance / dt;
|
|
278
|
+
const isSwipe = (distance >= threshold && dt <= maxDuration) ||
|
|
279
|
+
velocity >= minVelocity;
|
|
280
|
+
if (!isSwipe || distance === 0)
|
|
281
|
+
return;
|
|
282
|
+
const direction = horizontal
|
|
283
|
+
? travel > 0
|
|
284
|
+
? "right"
|
|
285
|
+
: "left"
|
|
286
|
+
: travel > 0
|
|
287
|
+
? "down"
|
|
288
|
+
: "up";
|
|
289
|
+
fire({
|
|
290
|
+
direction,
|
|
291
|
+
distance,
|
|
292
|
+
velocity,
|
|
293
|
+
duration: dt,
|
|
294
|
+
from: { x: startX, y: startY },
|
|
295
|
+
to: { x: ev.clientX, y: ev.clientY },
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
const cancel = () => {
|
|
299
|
+
window.removeEventListener("pointerup", up);
|
|
300
|
+
window.removeEventListener("pointercancel", cancel);
|
|
301
|
+
};
|
|
302
|
+
window.addEventListener("pointerup", up);
|
|
303
|
+
window.addEventListener("pointercancel", cancel);
|
|
304
|
+
};
|
|
305
|
+
// Late-bound refs (Solid assigns `ref` after mount) still get the grab.
|
|
306
|
+
createEffect(() => {
|
|
307
|
+
const el = ref();
|
|
308
|
+
if (!el)
|
|
309
|
+
return;
|
|
310
|
+
el.addEventListener("pointerdown", onDown);
|
|
311
|
+
onCleanup(() => el.removeEventListener("pointerdown", onDown));
|
|
312
|
+
});
|
|
313
|
+
return { lastSwipe, reset: () => setLastSwipe(null) };
|
|
314
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { createFontSwap, type FontSwapOptions, type FontSwapResult, createTyping
|
|
|
30
30
|
export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, type Easing, type EasingName, } from "./easing.js";
|
|
31
31
|
export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
|
|
32
32
|
export type { KineticTypeFrom, KineticTypeOptions, KineticTypeStatus, KineticTypeControls, MotionScene, ScenePlayerStatus, ScenePlayerControls, ShowreelScene, ShowreelSceneKind, CameraKeyframe, CameraOptions, ColorShiftOptions, ColorShiftStatus, ColorShiftControls, TransitionType, TransitionDirection, TransitionOptions, TransitionLayerStyle, TransitionStatus, TransitionControls, BeatOptions, BeatStatus, BeatControls, BeatCutOptions, } from "./motion.js";
|
|
33
|
-
export { createDrag, type DragStatus, type DragAxis, type DragConstraints, type DragEndInfo, type DragOptions, type DragControls, } from "./gesture.js";
|
|
33
|
+
export { createDrag, type DragStatus, type DragAxis, type DragConstraints, type DragEndInfo, type DragOptions, type DragControls, createSwipe, type SwipeDirection, type SwipeDetails, type SwipeOptions, type SwipeControls, } from "./gesture.js";
|
|
34
34
|
export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
|
|
35
35
|
export type { StreamRevealStatus, StreamRevealOptions, StreamRevealControls, AgentState, AgentStateTransition, AgentStateOptions, AgentStateControls, DriftSpecPrimitive, DriftSpecStep, DriftSpec, SpecPlayerStatus, SpecPlayerHooks, SpecPlayerControls, } from "./ai.js";
|
|
36
36
|
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { createGravity, createPendulum, createFling, } from "./physics.js";
|
|
|
29
29
|
export { createFontSwap, createTyping, createTextPhysics, createTextTunnel, createTextCutout, createTextGradient, createTextScramble, createTextWave, createCountUp, } from "./typography.js";
|
|
30
30
|
export { easings, cubicBezier, linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutExpo, easeOutBack, easeInBack, easeInOutBack, easeOutElastic, easeOutBounce, resolveEasing, } from "./easing.js";
|
|
31
31
|
export { createKineticType, createScenePlayer, createShowreel, createCamera, createColorShift, createTransition, createBeat, createBeatCuts, } from "./motion.js";
|
|
32
|
-
export { createDrag, } from "./gesture.js";
|
|
32
|
+
export { createDrag, createSwipe, } from "./gesture.js";
|
|
33
33
|
export { createStreamReveal, createAgentState, parseDriftSpec, createSpecPlayer, DriftSpecError, } from "./ai.js";
|
|
34
34
|
export { createTxLifecycle, createTicker, createMintReveal, createConnectButton, createAgentTx, } from "./web3.js";
|
|
35
35
|
export { createPoll, shortenAddress, isAddress, formatUnits, parseUnits, CHAINS, createChain, createTokenPrice, createPriceChange, createPriceCompare, createGasPrice, createBalance, createTxReceipt, createBlockNumber, createChainlinkPrice, createNFTMetadata, createENS, createIdenticon, } from "./web3data.js";
|
package/package.json
CHANGED