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 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
@@ -43,5 +43,5 @@
43
43
  },
44
44
  "type": "module",
45
45
  "types": "./dist/index.d.ts",
46
- "version": "0.19.0"
46
+ "version": "0.20.0"
47
47
  }