@sigx/lynx-gestures 0.4.0 → 0.4.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.
Files changed (47) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Draggable.js +378 -0
  3. package/dist/components/Draggable.js.map +1 -0
  4. package/dist/components/Pressable.d.ts +5 -4
  5. package/dist/components/Pressable.d.ts.map +1 -1
  6. package/dist/components/Pressable.js +157 -0
  7. package/dist/components/Pressable.js.map +1 -0
  8. package/dist/components/ScrollView.js +85 -0
  9. package/dist/components/ScrollView.js.map +1 -0
  10. package/dist/components/Swipeable.js +165 -0
  11. package/dist/components/Swipeable.js.map +1 -0
  12. package/dist/components/Swiper.d.ts +65 -0
  13. package/dist/components/Swiper.d.ts.map +1 -0
  14. package/dist/components/Swiper.js +124 -0
  15. package/dist/components/Swiper.js.map +1 -0
  16. package/dist/index.d.ts +17 -13
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +28 -404
  19. package/dist/index.js.map +1 -1
  20. package/dist/scroll-context.js +3 -0
  21. package/dist/scroll-context.js.map +1 -0
  22. package/dist/types.js +2 -0
  23. package/dist/types.js.map +1 -0
  24. package/dist/use-pinch.d.ts +1 -1
  25. package/dist/use-pinch.d.ts.map +1 -1
  26. package/dist/use-pinch.js +106 -0
  27. package/dist/use-pinch.js.map +1 -0
  28. package/dist/use-rotation.d.ts +1 -1
  29. package/dist/use-rotation.d.ts.map +1 -1
  30. package/dist/use-rotation.js +117 -0
  31. package/dist/use-rotation.js.map +1 -0
  32. package/dist/use-swiper-dot-progress.d.ts +129 -0
  33. package/dist/use-swiper-dot-progress.d.ts.map +1 -0
  34. package/dist/use-swiper-dot-progress.js +141 -0
  35. package/dist/use-swiper-dot-progress.js.map +1 -0
  36. package/dist/utils.js +25 -0
  37. package/dist/utils.js.map +1 -0
  38. package/package.json +10 -9
  39. package/src/components/Draggable.tsx +1 -1
  40. package/src/components/Pressable.tsx +44 -18
  41. package/src/components/ScrollView.tsx +1 -1
  42. package/src/components/Swipeable.tsx +1 -1
  43. package/src/components/Swiper.tsx +204 -0
  44. package/src/index.ts +27 -13
  45. package/src/use-pinch.ts +2 -2
  46. package/src/use-rotation.ts +2 -2
  47. package/src/use-swiper-dot-progress.ts +231 -0
package/README.md CHANGED
@@ -209,6 +209,48 @@ The component handles the inline `'main thread'` worklet, the SharedValue writes
209
209
 
210
210
  ---
211
211
 
212
+ ### `<Swiper>` and headless dot hooks
213
+
214
+ `<Swiper>` is a paged horizontal carousel that re-uses the platform's native `<scroll-view paging-enabled>` for snap-to-page (deceleration, overscroll, fling — all free), and writes the live pixel offset into a `SharedValue<number>` on every MT frame. Pair it with the headless `useSwiperDot*` hooks to build any indicator visual.
215
+
216
+ ```tsx
217
+ import { useSharedValue } from '@sigx/lynx';
218
+ import { signal } from '@sigx/lynx';
219
+ import { Swiper, useSwiperDotProgress } from '@sigx/lynx-gestures';
220
+
221
+ const offset = useSharedValue(0);
222
+ const pageIdx = signal({ value: 0 });
223
+
224
+ <Swiper offset={offset} index={pageIdx} width={pageWidth}>
225
+ <view style={{ width: pageWidth + 'px' }}>…page 1…</view>
226
+ <view style={{ width: pageWidth + 'px' }}>…page 2…</view>
227
+ <view style={{ width: pageWidth + 'px' }}>…page 3…</view>
228
+ </Swiper>
229
+ ```
230
+
231
+ #### Headless indicator hooks
232
+
233
+ Every indicator hook returns a `MainThreadRef<MainThread.Element | null>` that you spread onto whatever view you want animated. The hook owns the `useAnimatedStyle` call-site so you don't redo the triangular-window math. Pick one based on which CSS channel you want to drive:
234
+
235
+ | Hook | Channel(s) | Use case |
236
+ | -------------------------- | ---------------------- | ------------------------------------------- |
237
+ | `useSwiperDotProgress` | `opacity` | Crossfade between two colour layers. |
238
+ | `useSwiperDotScale` | `scale` (uniform) | Pulse / grow the active dot symmetrically. |
239
+ | `useSwiperDotGrowX` | `scaleX` (transform) | Pill / bar that stretches horizontally. |
240
+ | `useSwiperDotWidth` | `width` (layout px) | Same look as `GrowX` but reflows neighbours.|
241
+ | `useSwiperDotTranslate` | `translateX` (track) | Single thumb that slides across the whole strip. |
242
+
243
+ Example — a minimal opacity-crossfade dot:
244
+
245
+ ```tsx
246
+ const ref = useSwiperDotProgress({ offset, pageWidth, index: i });
247
+ <view main-thread:ref={ref} style={{ opacity: '0' }} />
248
+ ```
249
+
250
+ For a fully themed indicator (5 ready-made variants — dots, bar, pill, numbered, scale-pulse), use `<SwiperIndicator>` from [`@sigx/lynx-daisyui`](../lynx-daisyui).
251
+
252
+ ---
253
+
212
254
  ## Animation primitives
213
255
 
214
256
  > The cross-thread primitive — `useSharedValue`, `SharedValue`, `useAnimatedStyle` — lives in [`@sigx/lynx`](../lynx#sharedvalue--the-cross-thread-primitive) since 0.3.0. Import from `@sigx/lynx` directly:
@@ -0,0 +1,378 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component, useMainThreadRef, useSharedValue, useAnimatedStyle, runOnBackground, Gesture, useGestureDetector, } from '@sigx/lynx';
3
+ import { useScrollContext } from '../scroll-context.js';
4
+ /**
5
+ * MT-thread draggable container, built on the native gesture arena via
6
+ * `Gesture.Pan()`. The bound element's transform is driven by two
7
+ * `useAnimatedStyle` bindings (one per axis) — the same primitive any user
8
+ * could compose. The Pan onUpdate worklet writes to the SharedValues; the
9
+ * bridge applies the transform on the next flush boundary, composing the
10
+ * two bindings into a single `setStyleProperties({ transform })` call.
11
+ *
12
+ * Because the visible position is bridge-driven rather than written directly
13
+ * by the worklet, external animation of `translateX`/`translateY` (e.g.
14
+ * `withSpring(tx, 0)` to spring back to origin after release) moves the
15
+ * element visually for free — the binding picks up whichever SV write
16
+ * happened most recently, regardless of who wrote it.
17
+ *
18
+ * `dragStart` and `dragEnd` are dispatched to BG via `runOnBackground` (low
19
+ * frequency, cross-thread is fine).
20
+ *
21
+ * Unlike the prior `bindtouch*`-based implementation, the native pan gesture
22
+ * arena handles multi-touch correctly (secondary fingers don't cancel the
23
+ * primary drag).
24
+ *
25
+ * **Scroll composition** (Phase 2.12.3): Lynx's `<scroll-view>` doesn't
26
+ * participate in the new gesture arena, so without coordination both pan
27
+ * and scroll would fire concurrently. `<Draggable>` reads `useScrollContext`
28
+ * at setup; if a parent `<ScrollView>` is in scope, the BG-side dragStart/
29
+ * dragEnd flips `scrollCtx.dragging` automatically — the parent's
30
+ * `enable-scroll` is gated on that signal, so the UIKit pan recognizer
31
+ * yields for the duration of the drag. No consumer wiring required.
32
+ *
33
+ * **Edge-scroll** (Phase 2.13): pass `edgeScroll` to auto-scroll the parent
34
+ * `<ScrollView>` when the finger nears its viewport edge during a drag —
35
+ * the standard drag-to-reorder pattern (Apple Mail, iOS Reminders). The
36
+ * scroll axis follows `scrollOrientation` as published through the context.
37
+ * Inside the threshold zone the scroll velocity ramps from 0 at the
38
+ * threshold boundary to `maxSpeed` at the edge. Quietly no-ops if
39
+ * `edgeScroll` is unset OR the Draggable isn't nested in a ScrollView.
40
+ *
41
+ * Note on the native event payload: Lynx's pan handler emits `pageX`/`pageY`
42
+ * but no `translationX`/`velocityX` — we compute deltas and velocity from
43
+ * pageX/pageY ourselves (same as the prior touch-based implementation).
44
+ */
45
+ export const Draggable = component(({ props, slots, emit }) => {
46
+ const elRef = useMainThreadRef(null);
47
+ // Always allocate fallback SharedValues — hooks must run unconditionally.
48
+ const ownTx = useSharedValue(0);
49
+ const ownTy = useSharedValue(0);
50
+ // Pick once at setup so useAnimatedStyle bindings + worklet `_c` captures
51
+ // hold stable refs. Convention (also used by <ScrollView>): gesture-prop
52
+ // SVs are allocated once at the parent and don't swap across renders.
53
+ const tx = props.translateX ?? ownTx;
54
+ const ty = props.translateY ?? ownTy;
55
+ // Bridge tx/ty → element transform on every flush boundary. Composes with
56
+ // any external animation that mutates the same SVs.
57
+ useAnimatedStyle(elRef, tx, 'translateX');
58
+ useAnimatedStyle(elRef, ty, 'translateY');
59
+ const drag = useMainThreadRef({
60
+ startPageX: 0, startPageY: 0,
61
+ offsetX: 0, offsetY: 0,
62
+ prevPageX: 0, prevPageY: 0, prevTime: 0,
63
+ vx: 0, vy: 0,
64
+ lastPageX: 0, lastPageY: 0,
65
+ scrollViewLeft: 0, scrollViewTop: 0,
66
+ scrollViewWidth: 0, scrollViewHeight: 0,
67
+ edgeScrollActive: false,
68
+ lastScrollX: 0, lastScrollY: 0,
69
+ });
70
+ // Coordinate with the parent <ScrollView> (Phase 2.12.3): toggle its
71
+ // dragging signal during our gesture so its UIScrollView pan recognizer
72
+ // yields. Null when no ancestor ScrollView; the BG arrows below null-check.
73
+ const scrollCtx = useScrollContext();
74
+ // Pan config is read once at setup. Worklet bodies capture the snapshot
75
+ // via SWC's `_c` mechanism; runtime prop changes won't update an active
76
+ // gesture, but axis/threshold/clamps are render-stable in practice.
77
+ const axis = props.axis ?? 'both';
78
+ const threshold = props.threshold ?? 0;
79
+ const snapBack = props.snapBack ?? false;
80
+ const minX = props.minX;
81
+ const maxX = props.maxX;
82
+ const minY = props.minY;
83
+ const maxY = props.maxY;
84
+ // Phase 2.13: edge-scroll config. Normalized to plain numbers/booleans so
85
+ // worklet `_c` captures stay shape-stable. `edgeScrollEnabled` gates the
86
+ // viewport-measurement and rAF-tick paths in onStart/onUpdate; falsey
87
+ // means the new code paths short-circuit and behave identically to the
88
+ // pre-2.13 Draggable. `??` (not `||`) so an explicit `0` override for the
89
+ // edge zone or speed cap is preserved.
90
+ const edgeScrollProp = props.edgeScroll ?? false;
91
+ const edgeScrollEnabled = edgeScrollProp !== false;
92
+ const edgeScrollThreshold = (typeof edgeScrollProp === 'object' ? edgeScrollProp.threshold : undefined) ?? 50;
93
+ const edgeScrollMaxSpeed = (typeof edgeScrollProp === 'object' ? edgeScrollProp.maxSpeed : undefined) ?? 800;
94
+ // Captured at setup so the onUpdate tick reads a stable axis. `<ScrollView>`
95
+ // captures `scroll-orientation` once at setup too, so this stays consistent.
96
+ const scrollOrientation = scrollCtx?.scrollOrientation ?? 'vertical';
97
+ const pan = Gesture.Pan()
98
+ .minDistance(threshold)
99
+ // Empty onBegin is load-bearing on iOS: LynxPanGestureHandler bails out
100
+ // of onStart/onEnd unless `_isInvokedBegin` is YES, and that flag is
101
+ // only set inside the native onBegin handler — which itself short-
102
+ // circuits when no callback is registered. Registering any onBegin
103
+ // (even a no-op) gates the begin path open so onStart and onEnd fire.
104
+ .onBegin(() => {
105
+ 'main thread';
106
+ })
107
+ .onStart((e) => {
108
+ 'main thread';
109
+ // Pan event payload: { type, timestamp, target, currentTarget,
110
+ // params: { pageX, pageY, x, y, clientX, clientY,
111
+ // scrollX, scrollY, isAtStart, isAtEnd,
112
+ // type } , detail: <copy of params> }
113
+ // pageX/pageY are nested under params; the top-level event has only
114
+ // dispatch metadata.
115
+ const p = e && e.params;
116
+ const pageX = (p && p.pageX) || 0;
117
+ const pageY = (p && p.pageY) || 0;
118
+ drag.current.startPageX = pageX;
119
+ drag.current.startPageY = pageY;
120
+ drag.current.offsetX = tx.current.value;
121
+ drag.current.offsetY = ty.current.value;
122
+ drag.current.prevPageX = pageX;
123
+ drag.current.prevPageY = pageY;
124
+ drag.current.prevTime = Date.now();
125
+ drag.current.vx = 0;
126
+ drag.current.vy = 0;
127
+ drag.current.lastPageX = pageX;
128
+ drag.current.lastPageY = pageY;
129
+ drag.current.edgeScrollActive = false;
130
+ // Lazy viewport measurement for edge-scroll. Reads computed size of
131
+ // the parent <scroll-view> via the ref the context publishes; assumes
132
+ // page-rooted (top-left at page origin) which holds for the showcase
133
+ // and the standard "list takes the whole screen" pattern. Refine via
134
+ // a `boundingClientRect` invoke (returns Promise — needs async-stash)
135
+ // if a nested-scroll-view consumer hits it.
136
+ if (edgeScrollEnabled && scrollCtx) {
137
+ const svRef = scrollCtx.scrollViewRef.current;
138
+ if (svRef) {
139
+ // Use `boundingClientRect` over `getComputedStyleProperty`: it
140
+ // returns page-relative geometry that's consistent across iOS +
141
+ // Android. `getComputedStyleProperty('height')` sometimes returns
142
+ // unresolved `100vh`-style strings or content heights on Android,
143
+ // which made the bottom-edge zone unreachable on Pixel.
144
+ //
145
+ // The invoke is async (Promise-based) — the rect lands a tick or
146
+ // two after this call, so the first few onUpdate frames may see
147
+ // scrollView{Width,Height}=0 and skip the rAF schedule. By the
148
+ // time the user has dragged anywhere meaningful, the rect is
149
+ // populated and edge-scroll engages.
150
+ const rectP = svRef.invoke('boundingClientRect', {});
151
+ if (rectP && typeof rectP.then === 'function') {
152
+ rectP.then((rect) => {
153
+ if (!rect || typeof rect !== 'object')
154
+ return;
155
+ const r = rect;
156
+ drag.current.scrollViewLeft = r.left || 0;
157
+ drag.current.scrollViewTop = r.top || 0;
158
+ drag.current.scrollViewWidth = r.width || 0;
159
+ drag.current.scrollViewHeight = r.height || 0;
160
+ }).catch(() => { });
161
+ }
162
+ }
163
+ // Seed last-known scroll offsets so the first tick's "actual delta"
164
+ // baselines correctly. After the first frame, the rAF tick keeps
165
+ // these in sync with the live offsetX/Y SVs.
166
+ drag.current.lastScrollX = scrollCtx.offsetX.current.value;
167
+ drag.current.lastScrollY = scrollCtx.offsetY.current.value;
168
+ }
169
+ runOnBackground((startX, startY) => {
170
+ if (scrollCtx)
171
+ scrollCtx.dragging.value = true;
172
+ emit('dragStart', { x: startX, y: startY });
173
+ })(tx.current.value, ty.current.value);
174
+ })
175
+ .onUpdate((e) => {
176
+ 'main thread';
177
+ const p = e && e.params;
178
+ const pageX = (p && p.pageX) || 0;
179
+ const pageY = (p && p.pageY) || 0;
180
+ let dx = pageX - drag.current.startPageX;
181
+ let dy = pageY - drag.current.startPageY;
182
+ if (axis === 'x')
183
+ dy = 0;
184
+ else if (axis === 'y')
185
+ dx = 0;
186
+ let newX = drag.current.offsetX + dx;
187
+ let newY = drag.current.offsetY + dy;
188
+ if (minX !== undefined && newX < minX)
189
+ newX = minX;
190
+ if (maxX !== undefined && newX > maxX)
191
+ newX = maxX;
192
+ if (minY !== undefined && newY < minY)
193
+ newY = minY;
194
+ if (maxY !== undefined && newY > maxY)
195
+ newY = maxY;
196
+ const now = Date.now();
197
+ const dt = Math.max(now - drag.current.prevTime, 1);
198
+ drag.current.vx = (pageX - drag.current.prevPageX) / dt;
199
+ drag.current.vy = (pageY - drag.current.prevPageY) / dt;
200
+ drag.current.prevPageX = pageX;
201
+ drag.current.prevPageY = pageY;
202
+ drag.current.prevTime = now;
203
+ drag.current.lastPageX = pageX;
204
+ drag.current.lastPageY = pageY;
205
+ tx.current.value = newX;
206
+ ty.current.value = newY;
207
+ // Drive the useAnimatedStyle bindings on the same frame. Inlined
208
+ // (rather than calling an imported helper) because plain function
209
+ // imports don't survive worklet `_c` capture — same constraint as
210
+ // <ScrollView> and @sigx/lynx-motion's animate().
211
+ const __flush = globalThis['__FlushElementTree'];
212
+ if (__flush)
213
+ __flush();
214
+ // Phase 2.13 edge-scroll: enter the rAF loop when the finger crosses
215
+ // into the threshold zone. The tick closure self-cancels when
216
+ // `edgeScrollActive` flips false (onEnd) or the finger leaves the
217
+ // zone (zero velocity).
218
+ if (edgeScrollEnabled && scrollCtx && !drag.current.edgeScrollActive) {
219
+ const w = drag.current.scrollViewWidth;
220
+ const h = drag.current.scrollViewHeight;
221
+ if (w > 0 && h > 0) {
222
+ let inEdge = false;
223
+ if (scrollOrientation === 'vertical') {
224
+ const top = drag.current.scrollViewTop;
225
+ const py = drag.current.lastPageY;
226
+ const topDist = py - top;
227
+ const botDist = (top + h) - py;
228
+ inEdge = topDist < edgeScrollThreshold || botDist < edgeScrollThreshold;
229
+ }
230
+ else {
231
+ const left = drag.current.scrollViewLeft;
232
+ const px = drag.current.lastPageX;
233
+ const leftDist = px - left;
234
+ const rightDist = (left + w) - px;
235
+ inEdge = leftDist < edgeScrollThreshold || rightDist < edgeScrollThreshold;
236
+ }
237
+ if (inEdge) {
238
+ drag.current.edgeScrollActive = true;
239
+ // Inner arrow runs on MT (we're already inside an MT worklet
240
+ // body); rAF stashes the closure across frames in the MT VM.
241
+ // No `'main thread'` directive needed — the directive marks
242
+ // function bodies that cross threads, and we never leave MT.
243
+ const tick = () => {
244
+ if (!drag.current.edgeScrollActive)
245
+ return;
246
+ const ref = scrollCtx.scrollViewRef.current;
247
+ if (!ref) {
248
+ drag.current.edgeScrollActive = false;
249
+ return;
250
+ }
251
+ let velocity = 0;
252
+ if (scrollOrientation === 'vertical') {
253
+ const py2 = drag.current.lastPageY;
254
+ const top2 = drag.current.scrollViewTop;
255
+ const h2 = drag.current.scrollViewHeight;
256
+ const topDist2 = py2 - top2;
257
+ const botDist2 = (top2 + h2) - py2;
258
+ if (topDist2 < edgeScrollThreshold) {
259
+ const t = topDist2 < 0 ? 0 : topDist2;
260
+ velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);
261
+ }
262
+ else if (botDist2 < edgeScrollThreshold) {
263
+ const t = botDist2 < 0 ? 0 : botDist2;
264
+ velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);
265
+ }
266
+ }
267
+ else {
268
+ const px2 = drag.current.lastPageX;
269
+ const left2 = drag.current.scrollViewLeft;
270
+ const w2 = drag.current.scrollViewWidth;
271
+ const leftDist2 = px2 - left2;
272
+ const rightDist2 = (left2 + w2) - px2;
273
+ if (leftDist2 < edgeScrollThreshold) {
274
+ const t = leftDist2 < 0 ? 0 : leftDist2;
275
+ velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);
276
+ }
277
+ else if (rightDist2 < edgeScrollThreshold) {
278
+ const t = rightDist2 < 0 ? 0 : rightDist2;
279
+ velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);
280
+ }
281
+ }
282
+ if (velocity === 0) {
283
+ drag.current.edgeScrollActive = false;
284
+ return;
285
+ }
286
+ // Scroll-delta compensation: when content scrolls by `delta`,
287
+ // the Draggable's layout position moves by `-delta` (it's a
288
+ // child of the content). Without compensation the box drifts
289
+ // away from the finger as the page scrolls.
290
+ //
291
+ // We use the *actual* delivered scroll delta (read from
292
+ // offsetX/Y the bindscroll worklet maintains), not the
293
+ // velocity-based request. When the scroll-view clamps at the
294
+ // top/bottom (already at the edge), the actual delta is zero
295
+ // and we skip compensation — otherwise the box would keep
296
+ // drifting off-screen as we issue scrollBy calls the native
297
+ // side rejects.
298
+ //
299
+ // There's a one-frame lag (this tick reads the previous
300
+ // frame's actual delta), but it's imperceptible at 60fps.
301
+ const currScrollX = scrollCtx.offsetX.current.value;
302
+ const currScrollY = scrollCtx.offsetY.current.value;
303
+ const actualDX = currScrollX - drag.current.lastScrollX;
304
+ const actualDY = currScrollY - drag.current.lastScrollY;
305
+ drag.current.lastScrollX = currScrollX;
306
+ drag.current.lastScrollY = currScrollY;
307
+ if (scrollOrientation === 'vertical') {
308
+ if (actualDY !== 0) {
309
+ drag.current.offsetY += actualDY;
310
+ ty.current.value += actualDY;
311
+ }
312
+ }
313
+ else {
314
+ if (actualDX !== 0) {
315
+ drag.current.offsetX += actualDX;
316
+ tx.current.value += actualDX;
317
+ }
318
+ }
319
+ // 60 fps tick → offset (pt/frame) = velocity (pt/sec) / 60.
320
+ // scrollBy on a vertical scroll-view ignores the X component
321
+ // of the offset (and vice-versa per
322
+ // LynxUIScrollViewInternal.m:269), so a single signed
323
+ // `offset` works for both axes.
324
+ //
325
+ // `invoke()` already calls `__FlushElementTree()` internally
326
+ // (`MTElementWrapper.invoke` sequences `__InvokeUIMethod` →
327
+ // `__FlushElementTree` synchronously inside the Promise
328
+ // constructor), which picks up the SV write above. No
329
+ // explicit flush needed — adding one would double the
330
+ // per-frame work and contributes to scroll stutter.
331
+ const delta = velocity / 60;
332
+ const p2 = ref.invoke('scrollBy', { offset: delta });
333
+ if (p2 && typeof p2.catch === 'function')
334
+ p2.catch(() => { });
335
+ const raf = globalThis['requestAnimationFrame'];
336
+ if (raf)
337
+ raf(tick);
338
+ else
339
+ drag.current.edgeScrollActive = false;
340
+ };
341
+ const raf = globalThis['requestAnimationFrame'];
342
+ if (raf)
343
+ raf(tick);
344
+ else
345
+ drag.current.edgeScrollActive = false;
346
+ }
347
+ }
348
+ }
349
+ })
350
+ .onEnd(() => {
351
+ 'main thread';
352
+ // Stop the edge-scroll rAF loop (if any). The tick self-cancels next
353
+ // frame on the flag flip.
354
+ drag.current.edgeScrollActive = false;
355
+ if (snapBack) {
356
+ tx.current.value = 0;
357
+ ty.current.value = 0;
358
+ const __flush = globalThis['__FlushElementTree'];
359
+ if (__flush)
360
+ __flush();
361
+ }
362
+ // Capture MT values into locals before crossing back to BG —
363
+ // `tx.current.value` on the BG side reads the initial snapshot, not
364
+ // the live drag position. Same goes for `drag.current.vx/vy`.
365
+ const endX = tx.current.value;
366
+ const endY = ty.current.value;
367
+ const endVx = drag.current.vx;
368
+ const endVy = drag.current.vy;
369
+ runOnBackground((x, y, vx, vy) => {
370
+ if (scrollCtx)
371
+ scrollCtx.dragging.value = false;
372
+ emit('dragEnd', { x, y, vx, vy });
373
+ })(endX, endY, endVx, endVy);
374
+ });
375
+ useGestureDetector(elRef, pan);
376
+ return () => (_jsx("view", { class: props.class, style: props.style, "main-thread:ref": elRef, children: slots.default?.() }));
377
+ });
378
+ //# sourceMappingURL=Draggable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Draggable.js","sourceRoot":"","sources":["../../src/components/Draggable.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { type Define } from '@sigx/lynx';
2
- export type PressableProps = Define.Prop<'pressedOpacity', number, false> & Define.Prop<'pressedScale', number, false> & Define.Prop<'longPressDuration', number, false> & Define.Prop<'maxDistance', number, false> & Define.Prop<'disabled', boolean, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Slot<'default'> & Define.Event<'press', void> & Define.Event<'longPress', void>;
2
+ export type PressableProps = Define.Prop<'pressedOpacity', number, false> & Define.Prop<'pressedScale', number, false> & Define.Prop<'longPressDuration', number, false> & Define.Prop<'maxDistance', number, false> & Define.Prop<'disabled', boolean, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Prop<'accessibility-element', boolean, false> & Define.Prop<'accessibility-label', string, false> & Define.Prop<'accessibility-role', string, false> & Define.Prop<'accessibility-trait', string, false> & Define.Prop<'accessibility-status', string, false> & Define.Slot<'default'> & Define.Event<'press', void> & Define.Event<'longPress', void>;
3
3
  /**
4
4
  * MT-thread tap + long-press recognizer with built-in pressed-state visual
5
5
  * feedback (opacity + scale). Press and long-press callbacks are dispatched
@@ -30,9 +30,10 @@ export type PressableProps = Define.Prop<'pressedOpacity', number, false> & Defi
30
30
  * `LongPress.onEnd` skips press emission when the touch drifted past
31
31
  * the threshold (matching Tap's success criteria).
32
32
  *
33
- * Disabled is captured at setup; runtime toggling won't update an active
34
- * gesture's behavior. Wrap the parent in conditional rendering for now if
35
- * dynamic disable is needed.
33
+ * `disabled` reads live from a `MainThreadRef` so flipping it after mount
34
+ * (e.g. a `<Button loading>` toggling on) immediately suppresses both
35
+ * visual feedback and emit, without remounting the component or the
36
+ * underlying gesture registration.
36
37
  */
37
38
  export declare const Pressable: import("@sigx/runtime-core").ComponentFactory<PressableProps, void, {
38
39
  default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
@@ -1 +1 @@
1
- {"version":3,"file":"Pressable.d.ts","sourceRoot":"","sources":["../../src/components/Pressable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GACtB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC5C,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1C,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,GACzC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,GAC3B,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AASpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,SAAS;;EAqHpB,CAAC"}
1
+ {"version":3,"file":"Pressable.d.ts","sourceRoot":"","sources":["../../src/components/Pressable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GACtB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC5C,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1C,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,GACzC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAO5D,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,OAAO,EAAE,KAAK,CAAC,GACpD,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,MAAM,EAAE,KAAK,CAAC,GACjD,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,EAAE,KAAK,CAAC,GAChD,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,MAAM,EAAE,KAAK,CAAC,GACjD,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,MAAM,EAAE,KAAK,CAAC,GAClD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,GAC3B,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AASpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,SAAS;;EAmIpB,CAAC"}
@@ -0,0 +1,157 @@
1
+ import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
2
+ import { component, useMainThreadRef, runOnBackground, Gesture, useGestureDetector, } from '@sigx/lynx';
3
+ /**
4
+ * MT-thread tap + long-press recognizer with built-in pressed-state visual
5
+ * feedback (opacity + scale). Press and long-press callbacks are dispatched
6
+ * to BG via `runOnBackground` (low-frequency cross-thread is fine).
7
+ *
8
+ * Cross-platform gesture-arena quirks (Phase 2.12.1, observed on iOS Lynx
9
+ * 3.5 sim and Android Lynx 3.6 / Pixel 9 Pro XL) make this component a
10
+ * hybrid: it composes `Gesture.Tap()` + `Gesture.LongPress()` via
11
+ * `Simultaneous` AND adds an onEnd-fallback path inside LongPress, so press
12
+ * emission works on both platforms via different routes:
13
+ *
14
+ * - **Android**: `Tap.onStart` fires on touch-up (as documented). Press
15
+ * emits there; the LongPress fallback sees `pressEmitted=true` and
16
+ * skips. `Tap.onEnd` fires on the same touch-up — but iOS's premature
17
+ * onEnd (next bullet) means we can't safely reset styles here, so style
18
+ * reset lives in LongPress.onEnd.
19
+ * - **iOS**: `Tap.onEnd` fires ~6ms after touchstart (an arena
20
+ * fail/reset path that doesn't trigger on Android). `Tap.onStart`
21
+ * never fires for our composition. We rely on `LongPress.onEnd` to
22
+ * detect "lift before duration with no movement" and emit press from
23
+ * the fallback. `Gesture.Race` would be simpler in theory, but its
24
+ * `waitFor` deadlocks Tap on iOS — the arena dispatches Tap before
25
+ * LongPress reaches Fail state.
26
+ *
27
+ * State tracks `longPressFired` and `pressEmitted` so neither event
28
+ * double-fires regardless of which platform path resolves first.
29
+ * Movement past `maxDistance` is tracked from `e.params.pageX/pageY`;
30
+ * `LongPress.onEnd` skips press emission when the touch drifted past
31
+ * the threshold (matching Tap's success criteria).
32
+ *
33
+ * `disabled` reads live from a `MainThreadRef` so flipping it after mount
34
+ * (e.g. a `<Button loading>` toggling on) immediately suppresses both
35
+ * visual feedback and emit, without remounting the component or the
36
+ * underlying gesture registration.
37
+ */
38
+ export const Pressable = component(({ props, slots, emit }) => {
39
+ const elRef = useMainThreadRef(null);
40
+ const opacity = props.pressedOpacity ?? 0.6;
41
+ const scale = props.pressedScale ?? 1;
42
+ // longPressDuration === 0 disables long-press: we set minDuration to a
43
+ // huge value so the platform timer never fires; the iOS press fallback
44
+ // path still works because it's gated on `!longPressFired` (which stays
45
+ // false), and on Android the Tap.onStart path is unaffected.
46
+ const longPressDuration = props.longPressDuration ?? 500;
47
+ const minDuration = longPressDuration > 0 ? longPressDuration : 1000000;
48
+ const maxDistance = props.maxDistance ?? 10;
49
+ const maxDistanceSq = maxDistance * maxDistance;
50
+ // Reactive `disabled` — worklets read `disabledRef.current` so prop
51
+ // changes after mount take effect without re-registering the gesture.
52
+ // The render fn below keeps this ref in sync each pass.
53
+ const disabledRef = useMainThreadRef(!!props.disabled);
54
+ const state = useMainThreadRef({
55
+ longPressFired: false,
56
+ pressEmitted: false,
57
+ startPageX: 0,
58
+ startPageY: 0,
59
+ });
60
+ const tap = Gesture.Tap()
61
+ .maxDistance(maxDistance)
62
+ .onBegin((e) => {
63
+ 'main thread';
64
+ if (disabledRef.current)
65
+ return;
66
+ // Reset the cross-platform state on every fresh touch-down. Both
67
+ // Tap.onBegin and LongPress.onBegin fire — first one wins, second
68
+ // is a no-op because pressEmitted/longPressFired are already false.
69
+ state.current.longPressFired = false;
70
+ state.current.pressEmitted = false;
71
+ const p = e && e.params;
72
+ state.current.startPageX = (p && p.pageX) || 0;
73
+ state.current.startPageY = (p && p.pageY) || 0;
74
+ elRef.current?.setStyleProperties({
75
+ opacity: opacity,
76
+ transform: 'scale(' + scale + ')',
77
+ });
78
+ })
79
+ .onStart(() => {
80
+ 'main thread';
81
+ if (disabledRef.current)
82
+ return;
83
+ // Android path: Tap.onStart fires on touchend within maxDuration;
84
+ // emit press here. The LongPress.onEnd fallback below is gated on
85
+ // !pressEmitted so it won't double-fire on Android.
86
+ if (!state.current.pressEmitted) {
87
+ state.current.pressEmitted = true;
88
+ runOnBackground(() => { emit('press'); })();
89
+ }
90
+ });
91
+ // No Tap.onEnd: iOS fires it ~6ms after touchstart (arena fail/reset
92
+ // path), which would prematurely reset our press-state styles. Style
93
+ // reset lives in LongPress.onEnd, which fires only on real touch-up.
94
+ // Always pair Tap with a LongPress gesture — even when long-press is
95
+ // "disabled" by the consumer. LongPress.onEnd is the reliable terminal
96
+ // hook that resets the pressed visual state on touch-up across iOS +
97
+ // Android; without it the child would stay stuck in the pressed style.
98
+ // We disable long-press semantically (never fires) by pushing its
99
+ // minDuration past any realistic touch by setting it to MAX_SAFE_INTEGER
100
+ // while keeping the gesture registered for its onEnd lifecycle.
101
+ const longPressEnabled = longPressDuration > 0;
102
+ const longPress = Gesture.LongPress()
103
+ .minDuration(longPressEnabled ? minDuration : Number.MAX_SAFE_INTEGER)
104
+ .maxDistance(maxDistance)
105
+ .onBegin(() => {
106
+ 'main thread';
107
+ if (disabledRef.current)
108
+ return;
109
+ // Idempotent with Tap.onBegin — both fire on touch-down. State has
110
+ // already been initialised by Tap.onBegin (whichever fires first).
111
+ elRef.current?.setStyleProperties({
112
+ opacity: opacity,
113
+ transform: 'scale(' + scale + ')',
114
+ });
115
+ })
116
+ .onStart(() => {
117
+ 'main thread';
118
+ if (disabledRef.current)
119
+ return;
120
+ state.current.longPressFired = true;
121
+ runOnBackground(() => { emit('longPress'); })();
122
+ })
123
+ .onEnd((e) => {
124
+ 'main thread';
125
+ // Reset visual feedback regardless of how this terminal state was
126
+ // reached (success / fail / cancel / lift-before-duration).
127
+ elRef.current?.setStyleProperties({
128
+ opacity: 1,
129
+ transform: 'scale(1)',
130
+ });
131
+ if (disabledRef.current)
132
+ return;
133
+ // iOS fallback path. On iOS Tap.onStart never fires, so press would
134
+ // never emit without this. On Android this is a no-op because
135
+ // pressEmitted is already true (or longPressFired is true).
136
+ if (state.current.longPressFired || state.current.pressEmitted)
137
+ return;
138
+ const p = e && e.params;
139
+ if (!p)
140
+ return;
141
+ const dx = (p.pageX || 0) - state.current.startPageX;
142
+ const dy = (p.pageY || 0) - state.current.startPageY;
143
+ if (dx * dx + dy * dy > maxDistanceSq)
144
+ return; // movement-cancel
145
+ state.current.pressEmitted = true;
146
+ runOnBackground(() => { emit('press'); })();
147
+ });
148
+ const gesture = Gesture.Simultaneous(tap, longPress);
149
+ useGestureDetector(elRef, gesture);
150
+ return () => {
151
+ // Keep the reactive-disabled ref in sync with the prop on every render.
152
+ // Worklets read `.current` at call time, so this is the only writer.
153
+ disabledRef.current = !!props.disabled;
154
+ return (_jsx("view", { class: props.class, style: props.style, "main-thread:ref": elRef, "accessibility-element": props['accessibility-element'], "accessibility-label": props['accessibility-label'], "accessibility-role": props['accessibility-role'], "accessibility-trait": props['accessibility-trait'], "accessibility-status": props['accessibility-status'], children: slots.default?.() }));
155
+ };
156
+ });
157
+ //# sourceMappingURL=Pressable.js.map