@sigx/lynx-gestures 0.1.1 → 0.4.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 +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/use-pinch.d.ts +1 -1
- package/dist/use-pinch.d.ts.map +1 -1
- package/dist/use-rotation.d.ts +1 -1
- package/dist/use-rotation.d.ts.map +1 -1
- package/package.json +11 -8
- package/src/components/Draggable.tsx +1 -1
- package/src/components/ScrollView.tsx +1 -1
- package/src/components/Swipeable.tsx +1 -1
- package/src/index.ts +13 -13
- package/src/use-pinch.ts +2 -2
- package/src/use-rotation.ts +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Declarative, **frame-locked** gesture and animation primitives for [SignalX](htt
|
|
|
15
15
|
npm install @sigx/lynx-gestures
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
> Requires `@sigx/lynx` as a peer dependency. The build pipeline (`@sigx/lynx-plugin`) handles the `'main thread'` worklet transform automatically.
|
|
18
|
+
> Requires `@sigx/lynx` as a peer dependency. The build pipeline (`@sigx/lynx-plugin`) handles the `'main thread'` worklet transform automatically — including for this package's pre-built dist when installed via npm or pnpm.
|
|
19
19
|
|
|
20
20
|
## Quick start
|
|
21
21
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export { usePinch } from './use-pinch
|
|
2
|
-
export { useRotation } from './use-rotation
|
|
1
|
+
export { usePinch } from './use-pinch';
|
|
2
|
+
export { useRotation } from './use-rotation';
|
|
3
3
|
export { useSharedValue, SharedValue, useAnimatedValue, AnimatedValue, useAnimatedStyle, resetAnimatedStyleBindingIds, } from '@sigx/lynx';
|
|
4
4
|
export type { SharedValueState, AnimatedValueState, BuiltinMapperName, MapperParams, } from '@sigx/lynx';
|
|
5
|
-
export { Pressable } from './components/Pressable
|
|
6
|
-
export type { PressableProps } from './components/Pressable
|
|
7
|
-
export { Draggable } from './components/Draggable
|
|
8
|
-
export type { DraggableProps, DragEndDetail } from './components/Draggable
|
|
9
|
-
export { Swipeable } from './components/Swipeable
|
|
10
|
-
export type { SwipeableProps, SwipeSide } from './components/Swipeable
|
|
11
|
-
export { ScrollView } from './components/ScrollView
|
|
12
|
-
export type { ScrollViewProps } from './components/ScrollView
|
|
13
|
-
export { useScrollContext } from './scroll-context
|
|
14
|
-
export type { ScrollContext } from './scroll-context
|
|
15
|
-
export type { TouchPoint, TouchEvent, GesturePhase, GestureHandlers, PinchState, UsePinchOptions, UsePinchReturn, RotationState, UseRotationOptions, UseRotationReturn, } from './types
|
|
5
|
+
export { Pressable } from './components/Pressable';
|
|
6
|
+
export type { PressableProps } from './components/Pressable';
|
|
7
|
+
export { Draggable } from './components/Draggable';
|
|
8
|
+
export type { DraggableProps, DragEndDetail } from './components/Draggable';
|
|
9
|
+
export { Swipeable } from './components/Swipeable';
|
|
10
|
+
export type { SwipeableProps, SwipeSide } from './components/Swipeable';
|
|
11
|
+
export { ScrollView } from './components/ScrollView';
|
|
12
|
+
export type { ScrollViewProps } from './components/ScrollView';
|
|
13
|
+
export { useScrollContext } from './scroll-context';
|
|
14
|
+
export type { ScrollContext } from './scroll-context';
|
|
15
|
+
export type { TouchPoint, TouchEvent, GesturePhase, GestureHandlers, PinchState, UsePinchOptions, UsePinchReturn, RotationState, UseRotationOptions, UseRotationReturn, } from './types';
|
|
16
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAO7C,OAAO,EACL,cAAc,EACd,WAAW,EAEX,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,gBAAgB,EAEhB,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAK/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/utils.ts","../src/use-pinch.ts","../src/use-rotation.ts","../src/components/Pressable.tsx","../src/scroll-context.ts","../src/components/Draggable.tsx","../src/components/Swipeable.tsx","../src/components/ScrollView.tsx"],"sourcesContent":["// Geometry helpers used by the multi-touch JS-only fallback hooks\n// (`usePinch`, `useRotation`). The arena-driven gesture surface\n// (`Gesture.*` + `useGestureDetector`) computes its own deltas natively;\n// this file is only relevant while the platform's pinch/rotation handlers\n// are unfinished.\n\nexport function distance(x1: number, y1: number, x2: number, y2: number): number {\n return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);\n}\n\nexport function midpoint(x1: number, y1: number, x2: number, y2: number): [number, number] {\n return [(x1 + x2) / 2, (y1 + y2) / 2];\n}\n\n/** Signed angle in radians from p1 to p2, range (-π, π]. */\nexport function angle(x1: number, y1: number, x2: number, y2: number): number {\n return Math.atan2(y2 - y1, x2 - x1);\n}\n\n/** Shortest signed angular delta between two radians, range (-π, π]. */\nexport function angleDelta(from: number, to: number): number {\n let d = to - from;\n while (d > Math.PI) d -= 2 * Math.PI;\n while (d <= -Math.PI) d += 2 * Math.PI;\n return d;\n}\n","import { signal } from '@sigx/lynx';\nimport type { UsePinchOptions, UsePinchReturn, TouchEvent, PinchState, TouchPoint } from './types.js';\nimport { distance, midpoint } from './utils.js';\n\n/**\n * Two-finger pinch/zoom gesture.\n *\n * Tracks fingers manually since Lynx fires separate touchstart events per\n * finger (each with touches.length=1). Uses proximity-based matching on\n * touchmove since Lynx identifiers are unreliable across events.\n *\n * NOTE: Requires a device/environment that delivers multi-touch events to\n * the same element. Some Lynx hosts (e.g. Lynx Explorer on emulator) may\n * not support this — test on a physical device.\n */\nexport function usePinch(options: UsePinchOptions = {}): UsePinchReturn {\n const { onPinch } = options;\n\n const state = signal<PinchState>({\n phase: 'idle',\n scale: 1,\n focalX: 0,\n focalY: 0,\n });\n\n let baseDistance = 0;\n let active = false;\n let finger1: TouchPoint | null = null;\n let finger2: TouchPoint | null = null;\n\n function onTouchStart(e: TouchEvent): void {\n const t = e.touches[0];\n if (!t) return;\n\n if (!finger1) {\n finger1 = { ...t };\n } else if (!finger2) {\n finger2 = { ...t };\n active = true;\n baseDistance = distance(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n state.phase = 'began';\n state.scale = 1;\n state.focalX = fx;\n state.focalY = fy;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n if (!active || !finger1 || !finger2) return;\n\n const t = e.changedTouches[0];\n if (!t) return;\n\n // Determine which finger moved by proximity\n const dist1 = distance(t.pageX, t.pageY, finger1.pageX, finger1.pageY);\n const dist2 = distance(t.pageX, t.pageY, finger2.pageX, finger2.pageY);\n\n if (dist1 < dist2) {\n finger1 = { ...t };\n } else {\n finger2 = { ...t };\n }\n\n const currentDist = distance(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const scale = baseDistance > 0 ? currentDist / baseDistance : 1;\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n\n state.phase = 'active';\n state.scale = scale;\n state.focalX = fx;\n state.focalY = fy;\n onPinch?.(state as PinchState);\n }\n\n function onTouchEnd(): void {\n if (active) {\n state.phase = 'ended';\n onPinch?.(state as PinchState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function onTouchCancel(): void {\n if (active) {\n state.phase = 'cancelled';\n onPinch?.(state as PinchState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function reset(): void {\n active = false;\n finger1 = null;\n finger2 = null;\n state.phase = 'idle';\n state.scale = 1;\n state.focalX = 0;\n state.focalY = 0;\n }\n\n return {\n state,\n handlers: {\n bindtouchstart: onTouchStart,\n bindtouchmove: onTouchMove,\n bindtouchend: onTouchEnd,\n bindtouchcancel: onTouchCancel,\n },\n reset,\n };\n}\n","import { signal } from '@sigx/lynx';\nimport type {\n UseRotationOptions,\n UseRotationReturn,\n TouchEvent,\n TouchPoint,\n RotationState,\n} from './types.js';\nimport { angle, angleDelta, distance, midpoint } from './utils.js';\n\n/**\n * Two-finger rotation gesture.\n *\n * Tracks the angle of the line between two fingers; reports cumulative rotation\n * in radians from gesture start. Like usePinch, uses proximity-based finger\n * matching on touchmove (Lynx touch identifiers are not stable across events).\n *\n * NOTE: requires multi-touch delivery to the same element. Some Lynx hosts\n * (Lynx Explorer on emulator) may not support this — test on a physical device.\n */\nexport function useRotation(options: UseRotationOptions = {}): UseRotationReturn {\n const { onRotation } = options;\n\n const state = signal<RotationState>({\n phase: 'idle',\n rotation: 0,\n velocity: 0,\n focalX: 0,\n focalY: 0,\n });\n\n let baseAngle = 0;\n let prevAngle = 0;\n let prevTime = 0;\n let active = false;\n let finger1: TouchPoint | null = null;\n let finger2: TouchPoint | null = null;\n\n function onTouchStart(e: TouchEvent): void {\n const t = e.touches[0];\n if (!t) return;\n\n if (!finger1) {\n finger1 = { ...t };\n } else if (!finger2) {\n finger2 = { ...t };\n active = true;\n baseAngle = angle(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n prevAngle = baseAngle;\n prevTime = Date.now();\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n state.phase = 'began';\n state.rotation = 0;\n state.velocity = 0;\n state.focalX = fx;\n state.focalY = fy;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n if (!active || !finger1 || !finger2) return;\n const t = e.changedTouches[0];\n if (!t) return;\n\n const dist1 = distance(t.pageX, t.pageY, finger1.pageX, finger1.pageY);\n const dist2 = distance(t.pageX, t.pageY, finger2.pageX, finger2.pageY);\n if (dist1 < dist2) {\n finger1 = { ...t };\n } else {\n finger2 = { ...t };\n }\n\n const now = Date.now();\n const dt = Math.max(now - prevTime, 1);\n const currentAngle = angle(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const rotation = angleDelta(baseAngle, currentAngle);\n const velocity = angleDelta(prevAngle, currentAngle) / dt;\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n\n prevAngle = currentAngle;\n prevTime = now;\n\n state.phase = 'active';\n state.rotation = rotation;\n state.velocity = velocity;\n state.focalX = fx;\n state.focalY = fy;\n onRotation?.(state as RotationState);\n }\n\n function onTouchEnd(): void {\n if (active) {\n state.phase = 'ended';\n onRotation?.(state as RotationState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function onTouchCancel(): void {\n if (active) {\n state.phase = 'cancelled';\n onRotation?.(state as RotationState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function reset(): void {\n active = false;\n finger1 = null;\n finger2 = null;\n state.phase = 'idle';\n state.rotation = 0;\n state.velocity = 0;\n state.focalX = 0;\n state.focalY = 0;\n }\n\n return {\n state,\n handlers: {\n bindtouchstart: onTouchStart,\n bindtouchmove: onTouchMove,\n bindtouchend: onTouchEnd,\n bindtouchcancel: onTouchCancel,\n },\n reset,\n };\n}\n","import {\n component,\n useMainThreadRef,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\n\nexport type PressableProps =\n & Define.Prop<'pressedOpacity', number, false>\n & Define.Prop<'pressedScale', number, false>\n & Define.Prop<'longPressDuration', number, false>\n & Define.Prop<'maxDistance', number, false>\n & Define.Prop<'disabled', boolean, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'press', void>\n & Define.Event<'longPress', void>;\n\ninterface PressableMTState {\n longPressFired: boolean;\n pressEmitted: boolean;\n startPageX: number;\n startPageY: number;\n}\n\n/**\n * MT-thread tap + long-press recognizer with built-in pressed-state visual\n * feedback (opacity + scale). Press and long-press callbacks are dispatched\n * to BG via `runOnBackground` (low-frequency cross-thread is fine).\n *\n * Cross-platform gesture-arena quirks (Phase 2.12.1, observed on iOS Lynx\n * 3.5 sim and Android Lynx 3.6 / Pixel 9 Pro XL) make this component a\n * hybrid: it composes `Gesture.Tap()` + `Gesture.LongPress()` via\n * `Simultaneous` AND adds an onEnd-fallback path inside LongPress, so press\n * emission works on both platforms via different routes:\n *\n * - **Android**: `Tap.onStart` fires on touch-up (as documented). Press\n * emits there; the LongPress fallback sees `pressEmitted=true` and\n * skips. `Tap.onEnd` fires on the same touch-up — but iOS's premature\n * onEnd (next bullet) means we can't safely reset styles here, so style\n * reset lives in LongPress.onEnd.\n * - **iOS**: `Tap.onEnd` fires ~6ms after touchstart (an arena\n * fail/reset path that doesn't trigger on Android). `Tap.onStart`\n * never fires for our composition. We rely on `LongPress.onEnd` to\n * detect \"lift before duration with no movement\" and emit press from\n * the fallback. `Gesture.Race` would be simpler in theory, but its\n * `waitFor` deadlocks Tap on iOS — the arena dispatches Tap before\n * LongPress reaches Fail state.\n *\n * State tracks `longPressFired` and `pressEmitted` so neither event\n * double-fires regardless of which platform path resolves first.\n * Movement past `maxDistance` is tracked from `e.params.pageX/pageY`;\n * `LongPress.onEnd` skips press emission when the touch drifted past\n * the threshold (matching Tap's success criteria).\n *\n * Disabled is captured at setup; runtime toggling won't update an active\n * gesture's behavior. Wrap the parent in conditional rendering for now if\n * dynamic disable is needed.\n */\nexport const Pressable = component<PressableProps>(({ props, slots, emit }) => {\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n const opacity = props.pressedOpacity ?? 0.6;\n const scale = props.pressedScale ?? 1;\n // longPressDuration === 0 disables long-press: we set minDuration to a\n // huge value so the platform timer never fires; the iOS press fallback\n // path still works because it's gated on `!longPressFired` (which stays\n // false), and on Android the Tap.onStart path is unaffected.\n const longPressDuration = props.longPressDuration ?? 500;\n const minDuration = longPressDuration > 0 ? longPressDuration : 1_000_000;\n const maxDistance = props.maxDistance ?? 10;\n const maxDistanceSq = maxDistance * maxDistance;\n const disabled = props.disabled ?? false;\n\n const state = useMainThreadRef<PressableMTState>({\n longPressFired: false,\n pressEmitted: false,\n startPageX: 0,\n startPageY: 0,\n });\n\n const tap = Gesture.Tap()\n .maxDistance(maxDistance)\n .onBegin((e: any) => {\n 'main thread';\n if (disabled) return;\n // Reset the cross-platform state on every fresh touch-down. Both\n // Tap.onBegin and LongPress.onBegin fire — first one wins, second\n // is a no-op because pressEmitted/longPressFired are already false.\n state.current.longPressFired = false;\n state.current.pressEmitted = false;\n const p = e && e.params;\n state.current.startPageX = (p && p.pageX) || 0;\n state.current.startPageY = (p && p.pageY) || 0;\n elRef.current?.setStyleProperties({\n opacity: opacity,\n transform: 'scale(' + scale + ')',\n });\n })\n .onStart(() => {\n 'main thread';\n if (disabled) return;\n // Android path: Tap.onStart fires on touchend within maxDuration;\n // emit press here. The LongPress.onEnd fallback below is gated on\n // !pressEmitted so it won't double-fire on Android.\n if (!state.current.pressEmitted) {\n state.current.pressEmitted = true;\n runOnBackground(() => { emit('press'); })();\n }\n });\n // No Tap.onEnd: iOS fires it ~6ms after touchstart (arena fail/reset\n // path), which would prematurely reset our press-state styles. Style\n // reset lives in LongPress.onEnd, which fires only on real touch-up.\n\n // Always pair Tap with a LongPress gesture — even when long-press is\n // \"disabled\" by the consumer. LongPress.onEnd is the reliable terminal\n // hook that resets the pressed visual state on touch-up across iOS +\n // Android; without it the child would stay stuck in the pressed style.\n // We disable long-press semantically (never fires) by pushing its\n // minDuration past any realistic touch by setting it to MAX_SAFE_INTEGER\n // while keeping the gesture registered for its onEnd lifecycle.\n const longPressEnabled = longPressDuration > 0;\n const longPress = Gesture.LongPress()\n .minDuration(longPressEnabled ? minDuration : Number.MAX_SAFE_INTEGER)\n .maxDistance(maxDistance)\n .onBegin(() => {\n 'main thread';\n if (disabled) return;\n // Idempotent with Tap.onBegin — both fire on touch-down. State has\n // already been initialised by Tap.onBegin (whichever fires first).\n elRef.current?.setStyleProperties({\n opacity: opacity,\n transform: 'scale(' + scale + ')',\n });\n })\n .onStart(() => {\n 'main thread';\n if (disabled) return;\n state.current.longPressFired = true;\n runOnBackground(() => { emit('longPress'); })();\n })\n .onEnd((e: any) => {\n 'main thread';\n // Reset visual feedback regardless of how this terminal state was\n // reached (success / fail / cancel / lift-before-duration).\n elRef.current?.setStyleProperties({\n opacity: 1,\n transform: 'scale(1)',\n });\n if (disabled) return;\n // iOS fallback path. On iOS Tap.onStart never fires, so press would\n // never emit without this. On Android this is a no-op because\n // pressEmitted is already true (or longPressFired is true).\n if (state.current.longPressFired || state.current.pressEmitted) return;\n const p = e && e.params;\n if (!p) return;\n const dx = (p.pageX || 0) - state.current.startPageX;\n const dy = (p.pageY || 0) - state.current.startPageY;\n if (dx * dx + dy * dy > maxDistanceSq) return; // movement-cancel\n state.current.pressEmitted = true;\n runOnBackground(() => { emit('press'); })();\n });\n\n const gesture = Gesture.Simultaneous(tap, longPress);\n\n useGestureDetector(elRef, gesture);\n\n return () => (\n <view\n class={props.class}\n style={props.style}\n main-thread:ref={elRef}\n >\n {slots.default?.()}\n </view>\n );\n});\n","import {\n defineInjectable,\n type PrimitiveSignal,\n type SharedValue,\n type MainThreadRef,\n type MainThread,\n} from '@sigx/lynx';\n\n/**\n * Scroll-arena coordination context, provided by `<ScrollView>` and consumed\n * by descendant gesture components (`<Draggable>`, `<Swipeable>`).\n *\n * Why this exists: Lynx's `<scroll-view>` does NOT participate in the new\n * gesture arena (`LynxGestureArenaManager`) on iOS — its UIKit\n * `panGestureRecognizer` runs independently of arena gestures, so a Pan\n * registered on a descendant element fires concurrently with the parent\n * scroll. The visible result is \"drag works but the page scrolls too,\n * sliding the box away from the finger\".\n *\n * Workaround: the parent `<ScrollView>` exposes a BG-side `dragging` signal\n * that gates its `enable-scroll` prop. Gesture children flip the signal\n * during their lifecycle (onStart/onEnd → onDragStart/onDragEnd) so the\n * UIScrollView pan recognizer is disabled while a child gesture owns the\n * touch.\n *\n * This is a Phase 2.12 framework-level encapsulation of what consumers had\n * to wire by hand in Phase 2.11. A proper fix lives on the Lynx native\n * side: making `<scroll-view>`'s pan recognizer participate in the arena\n * (or yielding to arena recognizers in `shouldBeRequiredToFailByGestureRecognizer:`).\n * Until then, this is the cleanest the framework can be.\n *\n * Returns `null` when no parent `<ScrollView>` is in scope, so consumers\n * branch on presence:\n *\n * ```ts\n * const scrollCtx = useScrollContext();\n * // ... inside an onStart's runOnBackground arrow:\n * if (scrollCtx) scrollCtx.dragging.value = true;\n * ```\n *\n * Phase 2.13 extends the context with the scroll-view's element ref + live\n * scroll-position SVs + axis, so descendants can drive scroll directly\n * (edge-scroll while dragging, etc.) without re-piping refs through props.\n */\nexport interface ScrollContext {\n /** BG-side flag the parent `<ScrollView>` reads as `enable-scroll={!dragging.value}`. */\n dragging: PrimitiveSignal<boolean>;\n /**\n * MT element ref to the underlying `<scroll-view>`. Null until mounted.\n * Descendants call `scrollViewRef.current?.invoke('scrollBy', ...)` from\n * worklets to drive scroll programmatically.\n */\n scrollViewRef: MainThreadRef<MainThread.Element | null>;\n /**\n * Live horizontal scroll position. Same SV the consumer passes via\n * `<ScrollView offsetX={…}>` (or an internally-allocated fallback).\n */\n offsetX: SharedValue<number>;\n /**\n * Live vertical scroll position. Same SV the consumer passes via\n * `<ScrollView offsetY={…}>` (or an internally-allocated fallback).\n */\n offsetY: SharedValue<number>;\n /**\n * Scroll axis as configured on the `<scroll-view>`. Edge-scroll\n * descendants pick which edges to monitor based on this:\n * `'vertical'` → top/bottom; `'horizontal'` → left/right.\n */\n scrollOrientation: 'vertical' | 'horizontal';\n}\n\nexport const useScrollContext = defineInjectable<ScrollContext | null>(() => null);\n","import {\n component,\n useMainThreadRef,\n useSharedValue,\n useAnimatedStyle,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type SharedValue,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context.js';\n\nexport interface DragEndDetail {\n x: number;\n y: number;\n vx: number;\n vy: number;\n}\n\n/**\n * Edge-scroll configuration for `<Draggable edgeScroll>`. Either `true` for\n * default tuning, or an object overriding the defaults.\n */\nexport type EdgeScrollConfig = boolean | {\n /** Distance from viewport edge in pt where auto-scroll engages. Default 50. */\n threshold?: number;\n /** Maximum scroll velocity in pt/sec at the edge. Default 800. */\n maxSpeed?: number;\n};\n\nexport type DraggableProps =\n & Define.Prop<'axis', 'x' | 'y' | 'both', false>\n & Define.Prop<'threshold', number, false>\n & Define.Prop<'snapBack', boolean, false>\n & Define.Prop<'minX', number, false>\n & Define.Prop<'maxX', number, false>\n & Define.Prop<'minY', number, false>\n & Define.Prop<'maxY', number, false>\n & Define.Prop<'translateX', SharedValue<number>, false>\n & Define.Prop<'translateY', SharedValue<number>, false>\n & Define.Prop<'edgeScroll', EdgeScrollConfig, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'dragStart', { x: number; y: number }>\n & Define.Event<'dragEnd', DragEndDetail>;\n\ninterface DragMTState {\n startPageX: number;\n startPageY: number;\n offsetX: number;\n offsetY: number;\n prevPageX: number;\n prevPageY: number;\n prevTime: number;\n vx: number;\n vy: number;\n // Phase 2.13 edge-scroll state. Populated lazily in onStart when edgeScroll\n // is enabled and a parent ScrollView is in scope. Read by the rAF tick\n // closure scheduled from onUpdate.\n lastPageX: number;\n lastPageY: number;\n scrollViewLeft: number;\n scrollViewTop: number;\n scrollViewWidth: number;\n scrollViewHeight: number;\n edgeScrollActive: boolean;\n /**\n * Last observed parent ScrollView offset, sampled inside the rAF tick.\n * Used to compute the *actual* scroll delta between frames so the\n * compensation matches what the native scroll-view delivered (zero when\n * it clamped at top/bottom). Without this, holding past the top edge\n * keeps adding negative delta to ty even though the page can't scroll\n * any further, drifting the box off-screen.\n */\n lastScrollX: number;\n lastScrollY: number;\n}\n\n/**\n * MT-thread draggable container, built on the native gesture arena via\n * `Gesture.Pan()`. The bound element's transform is driven by two\n * `useAnimatedStyle` bindings (one per axis) — the same primitive any user\n * could compose. The Pan onUpdate worklet writes to the SharedValues; the\n * bridge applies the transform on the next flush boundary, composing the\n * two bindings into a single `setStyleProperties({ transform })` call.\n *\n * Because the visible position is bridge-driven rather than written directly\n * by the worklet, external animation of `translateX`/`translateY` (e.g.\n * `withSpring(tx, 0)` to spring back to origin after release) moves the\n * element visually for free — the binding picks up whichever SV write\n * happened most recently, regardless of who wrote it.\n *\n * `dragStart` and `dragEnd` are dispatched to BG via `runOnBackground` (low\n * frequency, cross-thread is fine).\n *\n * Unlike the prior `bindtouch*`-based implementation, the native pan gesture\n * arena handles multi-touch correctly (secondary fingers don't cancel the\n * primary drag).\n *\n * **Scroll composition** (Phase 2.12.3): Lynx's `<scroll-view>` doesn't\n * participate in the new gesture arena, so without coordination both pan\n * and scroll would fire concurrently. `<Draggable>` reads `useScrollContext`\n * at setup; if a parent `<ScrollView>` is in scope, the BG-side dragStart/\n * dragEnd flips `scrollCtx.dragging` automatically — the parent's\n * `enable-scroll` is gated on that signal, so the UIKit pan recognizer\n * yields for the duration of the drag. No consumer wiring required.\n *\n * **Edge-scroll** (Phase 2.13): pass `edgeScroll` to auto-scroll the parent\n * `<ScrollView>` when the finger nears its viewport edge during a drag —\n * the standard drag-to-reorder pattern (Apple Mail, iOS Reminders). The\n * scroll axis follows `scrollOrientation` as published through the context.\n * Inside the threshold zone the scroll velocity ramps from 0 at the\n * threshold boundary to `maxSpeed` at the edge. Quietly no-ops if\n * `edgeScroll` is unset OR the Draggable isn't nested in a ScrollView.\n *\n * Note on the native event payload: Lynx's pan handler emits `pageX`/`pageY`\n * but no `translationX`/`velocityX` — we compute deltas and velocity from\n * pageX/pageY ourselves (same as the prior touch-based implementation).\n */\nexport const Draggable = component<DraggableProps>(({ props, slots, emit }) => {\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Always allocate fallback SharedValues — hooks must run unconditionally.\n const ownTx = useSharedValue(0);\n const ownTy = useSharedValue(0);\n\n // Pick once at setup so useAnimatedStyle bindings + worklet `_c` captures\n // hold stable refs. Convention (also used by <ScrollView>): gesture-prop\n // SVs are allocated once at the parent and don't swap across renders.\n const tx = props.translateX ?? ownTx;\n const ty = props.translateY ?? ownTy;\n\n // Bridge tx/ty → element transform on every flush boundary. Composes with\n // any external animation that mutates the same SVs.\n useAnimatedStyle(elRef, tx, 'translateX');\n useAnimatedStyle(elRef, ty, 'translateY');\n\n const drag = useMainThreadRef<DragMTState>({\n startPageX: 0, startPageY: 0,\n offsetX: 0, offsetY: 0,\n prevPageX: 0, prevPageY: 0, prevTime: 0,\n vx: 0, vy: 0,\n lastPageX: 0, lastPageY: 0,\n scrollViewLeft: 0, scrollViewTop: 0,\n scrollViewWidth: 0, scrollViewHeight: 0,\n edgeScrollActive: false,\n lastScrollX: 0, lastScrollY: 0,\n });\n\n // Coordinate with the parent <ScrollView> (Phase 2.12.3): toggle its\n // dragging signal during our gesture so its UIScrollView pan recognizer\n // yields. Null when no ancestor ScrollView; the BG arrows below null-check.\n const scrollCtx = useScrollContext();\n\n // Pan config is read once at setup. Worklet bodies capture the snapshot\n // via SWC's `_c` mechanism; runtime prop changes won't update an active\n // gesture, but axis/threshold/clamps are render-stable in practice.\n const axis = props.axis ?? 'both';\n const threshold = props.threshold ?? 0;\n const snapBack = props.snapBack ?? false;\n const minX = props.minX;\n const maxX = props.maxX;\n const minY = props.minY;\n const maxY = props.maxY;\n\n // Phase 2.13: edge-scroll config. Normalized to plain numbers/booleans so\n // worklet `_c` captures stay shape-stable. `edgeScrollEnabled` gates the\n // viewport-measurement and rAF-tick paths in onStart/onUpdate; falsey\n // means the new code paths short-circuit and behave identically to the\n // pre-2.13 Draggable. `??` (not `||`) so an explicit `0` override for the\n // edge zone or speed cap is preserved.\n const edgeScrollProp = props.edgeScroll ?? false;\n const edgeScrollEnabled = edgeScrollProp !== false;\n const edgeScrollThreshold =\n (typeof edgeScrollProp === 'object' ? edgeScrollProp.threshold : undefined) ?? 50;\n const edgeScrollMaxSpeed =\n (typeof edgeScrollProp === 'object' ? edgeScrollProp.maxSpeed : undefined) ?? 800;\n // Captured at setup so the onUpdate tick reads a stable axis. `<ScrollView>`\n // captures `scroll-orientation` once at setup too, so this stays consistent.\n const scrollOrientation: 'vertical' | 'horizontal' = scrollCtx?.scrollOrientation ?? 'vertical';\n\n const pan = Gesture.Pan()\n .minDistance(threshold)\n // Empty onBegin is load-bearing on iOS: LynxPanGestureHandler bails out\n // of onStart/onEnd unless `_isInvokedBegin` is YES, and that flag is\n // only set inside the native onBegin handler — which itself short-\n // circuits when no callback is registered. Registering any onBegin\n // (even a no-op) gates the begin path open so onStart and onEnd fire.\n .onBegin(() => {\n 'main thread';\n })\n .onStart((e: any) => {\n 'main thread';\n // Pan event payload: { type, timestamp, target, currentTarget,\n // params: { pageX, pageY, x, y, clientX, clientY,\n // scrollX, scrollY, isAtStart, isAtEnd,\n // type } , detail: <copy of params> }\n // pageX/pageY are nested under params; the top-level event has only\n // dispatch metadata.\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const pageY = (p && p.pageY) || 0;\n drag.current.startPageX = pageX;\n drag.current.startPageY = pageY;\n drag.current.offsetX = tx.current.value;\n drag.current.offsetY = ty.current.value;\n drag.current.prevPageX = pageX;\n drag.current.prevPageY = pageY;\n drag.current.prevTime = Date.now();\n drag.current.vx = 0;\n drag.current.vy = 0;\n drag.current.lastPageX = pageX;\n drag.current.lastPageY = pageY;\n drag.current.edgeScrollActive = false;\n // Lazy viewport measurement for edge-scroll. Reads computed size of\n // the parent <scroll-view> via the ref the context publishes; assumes\n // page-rooted (top-left at page origin) which holds for the showcase\n // and the standard \"list takes the whole screen\" pattern. Refine via\n // a `boundingClientRect` invoke (returns Promise — needs async-stash)\n // if a nested-scroll-view consumer hits it.\n if (edgeScrollEnabled && scrollCtx) {\n const svRef = scrollCtx.scrollViewRef.current;\n if (svRef) {\n // Use `boundingClientRect` over `getComputedStyleProperty`: it\n // returns page-relative geometry that's consistent across iOS +\n // Android. `getComputedStyleProperty('height')` sometimes returns\n // unresolved `100vh`-style strings or content heights on Android,\n // which made the bottom-edge zone unreachable on Pixel.\n //\n // The invoke is async (Promise-based) — the rect lands a tick or\n // two after this call, so the first few onUpdate frames may see\n // scrollView{Width,Height}=0 and skip the rAF schedule. By the\n // time the user has dragged anywhere meaningful, the rect is\n // populated and edge-scroll engages.\n const rectP = svRef.invoke('boundingClientRect', {});\n if (rectP && typeof rectP.then === 'function') {\n rectP.then((rect: unknown) => {\n if (!rect || typeof rect !== 'object') return;\n const r = rect as { left?: number; top?: number; width?: number; height?: number };\n drag.current.scrollViewLeft = r.left || 0;\n drag.current.scrollViewTop = r.top || 0;\n drag.current.scrollViewWidth = r.width || 0;\n drag.current.scrollViewHeight = r.height || 0;\n }).catch(() => {});\n }\n }\n // Seed last-known scroll offsets so the first tick's \"actual delta\"\n // baselines correctly. After the first frame, the rAF tick keeps\n // these in sync with the live offsetX/Y SVs.\n drag.current.lastScrollX = scrollCtx.offsetX.current.value;\n drag.current.lastScrollY = scrollCtx.offsetY.current.value;\n }\n runOnBackground((startX: number, startY: number) => {\n if (scrollCtx) scrollCtx.dragging.value = true;\n emit('dragStart', { x: startX, y: startY });\n })(tx.current.value, ty.current.value);\n })\n .onUpdate((e: any) => {\n 'main thread';\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const pageY = (p && p.pageY) || 0;\n let dx = pageX - drag.current.startPageX;\n let dy = pageY - drag.current.startPageY;\n if (axis === 'x') dy = 0;\n else if (axis === 'y') dx = 0;\n let newX = drag.current.offsetX + dx;\n let newY = drag.current.offsetY + dy;\n if (minX !== undefined && newX < minX) newX = minX;\n if (maxX !== undefined && newX > maxX) newX = maxX;\n if (minY !== undefined && newY < minY) newY = minY;\n if (maxY !== undefined && newY > maxY) newY = maxY;\n const now = Date.now();\n const dt = Math.max(now - drag.current.prevTime, 1);\n drag.current.vx = (pageX - drag.current.prevPageX) / dt;\n drag.current.vy = (pageY - drag.current.prevPageY) / dt;\n drag.current.prevPageX = pageX;\n drag.current.prevPageY = pageY;\n drag.current.prevTime = now;\n drag.current.lastPageX = pageX;\n drag.current.lastPageY = pageY;\n tx.current.value = newX;\n ty.current.value = newY;\n // Drive the useAnimatedStyle bindings on the same frame. Inlined\n // (rather than calling an imported helper) because plain function\n // imports don't survive worklet `_c` capture — same constraint as\n // <ScrollView> and @sigx/lynx-motion's animate().\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n // Phase 2.13 edge-scroll: enter the rAF loop when the finger crosses\n // into the threshold zone. The tick closure self-cancels when\n // `edgeScrollActive` flips false (onEnd) or the finger leaves the\n // zone (zero velocity).\n if (edgeScrollEnabled && scrollCtx && !drag.current.edgeScrollActive) {\n const w = drag.current.scrollViewWidth;\n const h = drag.current.scrollViewHeight;\n if (w > 0 && h > 0) {\n let inEdge = false;\n if (scrollOrientation === 'vertical') {\n const top = drag.current.scrollViewTop;\n const py = drag.current.lastPageY;\n const topDist = py - top;\n const botDist = (top + h) - py;\n inEdge = topDist < edgeScrollThreshold || botDist < edgeScrollThreshold;\n } else {\n const left = drag.current.scrollViewLeft;\n const px = drag.current.lastPageX;\n const leftDist = px - left;\n const rightDist = (left + w) - px;\n inEdge = leftDist < edgeScrollThreshold || rightDist < edgeScrollThreshold;\n }\n if (inEdge) {\n drag.current.edgeScrollActive = true;\n // Inner arrow runs on MT (we're already inside an MT worklet\n // body); rAF stashes the closure across frames in the MT VM.\n // No `'main thread'` directive needed — the directive marks\n // function bodies that cross threads, and we never leave MT.\n const tick = (): void => {\n if (!drag.current.edgeScrollActive) return;\n const ref = scrollCtx.scrollViewRef.current;\n if (!ref) {\n drag.current.edgeScrollActive = false;\n return;\n }\n let velocity = 0;\n if (scrollOrientation === 'vertical') {\n const py2 = drag.current.lastPageY;\n const top2 = drag.current.scrollViewTop;\n const h2 = drag.current.scrollViewHeight;\n const topDist2 = py2 - top2;\n const botDist2 = (top2 + h2) - py2;\n if (topDist2 < edgeScrollThreshold) {\n const t = topDist2 < 0 ? 0 : topDist2;\n velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n } else if (botDist2 < edgeScrollThreshold) {\n const t = botDist2 < 0 ? 0 : botDist2;\n velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n }\n } else {\n const px2 = drag.current.lastPageX;\n const left2 = drag.current.scrollViewLeft;\n const w2 = drag.current.scrollViewWidth;\n const leftDist2 = px2 - left2;\n const rightDist2 = (left2 + w2) - px2;\n if (leftDist2 < edgeScrollThreshold) {\n const t = leftDist2 < 0 ? 0 : leftDist2;\n velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n } else if (rightDist2 < edgeScrollThreshold) {\n const t = rightDist2 < 0 ? 0 : rightDist2;\n velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n }\n }\n if (velocity === 0) {\n drag.current.edgeScrollActive = false;\n return;\n }\n // Scroll-delta compensation: when content scrolls by `delta`,\n // the Draggable's layout position moves by `-delta` (it's a\n // child of the content). Without compensation the box drifts\n // away from the finger as the page scrolls.\n //\n // We use the *actual* delivered scroll delta (read from\n // offsetX/Y the bindscroll worklet maintains), not the\n // velocity-based request. When the scroll-view clamps at the\n // top/bottom (already at the edge), the actual delta is zero\n // and we skip compensation — otherwise the box would keep\n // drifting off-screen as we issue scrollBy calls the native\n // side rejects.\n //\n // There's a one-frame lag (this tick reads the previous\n // frame's actual delta), but it's imperceptible at 60fps.\n const currScrollX = scrollCtx.offsetX.current.value;\n const currScrollY = scrollCtx.offsetY.current.value;\n const actualDX = currScrollX - drag.current.lastScrollX;\n const actualDY = currScrollY - drag.current.lastScrollY;\n drag.current.lastScrollX = currScrollX;\n drag.current.lastScrollY = currScrollY;\n if (scrollOrientation === 'vertical') {\n if (actualDY !== 0) {\n drag.current.offsetY += actualDY;\n ty.current.value += actualDY;\n }\n } else {\n if (actualDX !== 0) {\n drag.current.offsetX += actualDX;\n tx.current.value += actualDX;\n }\n }\n // 60 fps tick → offset (pt/frame) = velocity (pt/sec) / 60.\n // scrollBy on a vertical scroll-view ignores the X component\n // of the offset (and vice-versa per\n // LynxUIScrollViewInternal.m:269), so a single signed\n // `offset` works for both axes.\n //\n // `invoke()` already calls `__FlushElementTree()` internally\n // (`MTElementWrapper.invoke` sequences `__InvokeUIMethod` →\n // `__FlushElementTree` synchronously inside the Promise\n // constructor), which picks up the SV write above. No\n // explicit flush needed — adding one would double the\n // per-frame work and contributes to scroll stutter.\n const delta = velocity / 60;\n const p2 = ref.invoke('scrollBy', { offset: delta });\n if (p2 && typeof p2.catch === 'function') p2.catch(() => {});\n const raf = (globalThis as Record<string, unknown>)['requestAnimationFrame'] as\n ((cb: () => void) => void) | undefined;\n if (raf) raf(tick);\n else drag.current.edgeScrollActive = false;\n };\n const raf = (globalThis as Record<string, unknown>)['requestAnimationFrame'] as\n ((cb: () => void) => void) | undefined;\n if (raf) raf(tick);\n else drag.current.edgeScrollActive = false;\n }\n }\n }\n })\n .onEnd(() => {\n 'main thread';\n // Stop the edge-scroll rAF loop (if any). The tick self-cancels next\n // frame on the flag flip.\n drag.current.edgeScrollActive = false;\n if (snapBack) {\n tx.current.value = 0;\n ty.current.value = 0;\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n }\n // Capture MT values into locals before crossing back to BG —\n // `tx.current.value` on the BG side reads the initial snapshot, not\n // the live drag position. Same goes for `drag.current.vx/vy`.\n const endX = tx.current.value;\n const endY = ty.current.value;\n const endVx = drag.current.vx;\n const endVy = drag.current.vy;\n runOnBackground((x: number, y: number, vx: number, vy: number) => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('dragEnd', { x, y, vx, vy });\n })(endX, endY, endVx, endVy);\n });\n\n useGestureDetector(elRef, pan);\n\n return () => (\n <view\n class={props.class}\n style={props.style}\n main-thread:ref={elRef}\n >\n {slots.default?.()}\n </view>\n );\n});\n","import {\n component,\n useMainThreadRef,\n useSharedValue,\n useAnimatedStyle,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context.js';\n\nexport type SwipeSide = 'left' | 'right';\n\nexport type SwipeableProps =\n & Define.Prop<'leftActionsWidth', number, false>\n & Define.Prop<'rightActionsWidth', number, false>\n & Define.Prop<'snapThreshold', number, false>\n & Define.Prop<'snapDuration', number, false>\n & Define.Prop<'leftActions', () => unknown, false>\n & Define.Prop<'rightActions', () => unknown, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Prop<'foregroundStyle', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'swipeOpen', { side: SwipeSide }>\n & Define.Event<'swipeClose', void>;\n\ninterface SwipeMTState {\n startPageX: number;\n offsetX: number;\n /** Snapped resting position: 0, +leftWidth, or -rightWidth. */\n currentX: number;\n}\n\n/**\n * Horizontal swipe-to-reveal container, built on the native gesture arena\n * via `Gesture.Pan().axis('x')`. The foreground is dragged horizontally on\n * the MT thread; on release it snaps to one of three resting positions\n * (closed / open-left / open-right) using `MTElementWrapper.animate()`.\n * Open and close events are dispatched to BG via `runOnBackground`.\n *\n * Migrated from a 4-`bindtouch*`-worklet implementation to a single\n * `Gesture.Pan()` (Phase 2.12). Carries the same Phase 2.11 quirks:\n * - `.onBegin(() => {})` no-op is load-bearing on iOS Pan to gate\n * `_isInvokedBegin` open so onStart/onEnd fire.\n * - `e.params.pageX` (not `e.pageX`) — Lynx pan event nests the touch\n * payload under `params`.\n *\n * Supply `leftActions` and/or `rightActions` as render-prop functions:\n *\n * ```tsx\n * <Swipeable\n * rightActions={() => <view><text>Delete</text></view>}\n * onSwipeOpen={(e) => console.log('opened', e.side)}\n * >\n * <view><text>Row content</text></view>\n * </Swipeable>\n * ```\n *\n * **Scroll composition** (Phase 2.12.3): nesting `<Swipeable>` inside\n * `<ScrollView>` is automatic — `useScrollContext` is read at setup and\n * the BG-side onStart/onEnd handlers flip `scrollCtx.dragging` so the\n * parent yields its UIKit pan for the duration of the swipe. No consumer\n * wiring required.\n */\nexport const Swipeable = component<SwipeableProps>(({ props, slots, emit }) => {\n const fgRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Drive the foreground transform via a SharedValue so external animations\n // could compose if we ever wanted spring snaps. For now we still call\n // `.animate()` on the element directly for the snap; the SV is only the\n // intermediate write target during the drag.\n const tx = useSharedValue(0);\n useAnimatedStyle(fgRef, tx, 'translateX');\n\n const drag = useMainThreadRef<SwipeMTState>({\n startPageX: 0,\n offsetX: 0,\n currentX: 0,\n });\n\n // Coordinate with the parent <ScrollView> (Phase 2.12.3) — see Draggable\n // for the why. Null when no ancestor ScrollView.\n const scrollCtx = useScrollContext();\n\n const leftWidth = props.leftActionsWidth ?? 100;\n const rightWidth = props.rightActionsWidth ?? 100;\n const snapThreshold = props.snapThreshold ?? 60;\n const snapDuration = props.snapDuration ?? 200;\n const hasLeft = !!props.leftActions;\n const hasRight = !!props.rightActions;\n const upper = hasLeft ? leftWidth : 0;\n const lower = hasRight ? -rightWidth : 0;\n\n const pan = Gesture.Pan()\n .axis('x')\n // Empty onBegin gates `_isInvokedBegin` open on iOS so onStart/onEnd fire.\n .onBegin(() => {\n 'main thread';\n })\n .onStart((e: any) => {\n 'main thread';\n const p = e && e.params;\n drag.current.startPageX = (p && p.pageX) || 0;\n drag.current.offsetX = drag.current.currentX;\n // Tell the parent ScrollView (if any) we own the touch.\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = true;\n })();\n })\n .onUpdate((e: any) => {\n 'main thread';\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n let x = drag.current.offsetX + (pageX - drag.current.startPageX);\n if (x > upper) x = upper;\n if (x < lower) x = lower;\n tx.current.value = x;\n // Bridge the binding on the same frame so the foreground tracks the\n // finger without a vsync delay (same trick as Draggable).\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n })\n .onEnd(() => {\n 'main thread';\n const x = tx.current.value;\n // Snap to closest resting position.\n let target = 0;\n if (hasLeft && x > snapThreshold) target = leftWidth;\n else if (hasRight && x < -snapThreshold) target = -rightWidth;\n fgRef.current?.animate(\n [\n { transform: 'translateX(' + x + 'px)' },\n { transform: 'translateX(' + target + 'px)' },\n ],\n { duration: snapDuration, fill: 'forwards', easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' },\n )?.play();\n // Keep the SV in sync with the snap target so subsequent drags don't\n // jump back to the pre-animate position.\n tx.current.value = target;\n const wasOpen = drag.current.currentX !== 0;\n const nowOpen = target !== 0;\n drag.current.currentX = target;\n // Always release the parent ScrollView's claim, regardless of snap.\n // Bundled into the same runOnBackground call as the emit so we only\n // pay one cross-thread hop.\n if (nowOpen) {\n const side: SwipeSide = target > 0 ? 'left' : 'right';\n runOnBackground((s: SwipeSide) => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('swipeOpen', { side: s });\n })(side);\n } else if (wasOpen) {\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('swipeClose');\n })();\n } else {\n // Closed→closed: still need to release the ScrollView claim.\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n })();\n }\n });\n\n useGestureDetector(fgRef, pan);\n\n return () => (\n <view\n class={props.class}\n style={{\n position: 'relative',\n overflow: 'hidden',\n ...(props.style || {}),\n }}\n >\n {hasLeft ? (\n <view style={{\n position: 'absolute',\n left: '0',\n top: '0',\n bottom: '0',\n width: leftWidth + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n {props.leftActions!()}\n </view>\n ) : null}\n\n {hasRight ? (\n <view style={{\n position: 'absolute',\n right: '0',\n top: '0',\n bottom: '0',\n width: rightWidth + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n {props.rightActions!()}\n </view>\n ) : null}\n\n <view\n main-thread:ref={fgRef}\n style={{\n position: 'relative',\n ...(props.foregroundStyle || {}),\n }}\n >\n {slots.default?.()}\n </view>\n </view>\n );\n});\n","import {\n component,\n signal,\n useSharedValue,\n useMainThreadRef,\n defineProvide,\n type SharedValue,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context.js';\n\nexport type ScrollViewProps =\n & Define.Prop<'offsetX', SharedValue<number>, false>\n & Define.Prop<'offsetY', SharedValue<number>, false>\n & Define.Prop<'scroll-orientation', 'vertical' | 'horizontal', false>\n /**\n * Toggle native scroll responsiveness at runtime — set false to lock the\n * scroll-view (e.g. while a child `<Draggable>` is mid-drag, so Lynx's\n * native pan gesture doesn't steal the touch). Maps to Lynx's\n * `enable-scroll` attribute.\n */\n & Define.Prop<'enable-scroll', boolean, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\n/**\n * MT-thread `<scroll-view>` wrapper that mirrors scroll position into a\n * `SharedValue`. Pair with `useAnimatedStyle` for parallax / fade / scale\n * effects driven by scroll, all running on MT with zero per-frame thread\n * crossings.\n *\n * The component is the API; the inline `'main thread'` worklet, the\n * `__FlushElementTree()` trigger, and the runtime registration are all\n * internal. Users just pass a `SharedValue<number>` for the axis they care\n * about — same shape as `<Draggable translateX={tx}>`.\n *\n * @example Parallax header\n * ```tsx\n * const scrollY = useSharedValue(0);\n * const headerRef = useMainThreadRef<MainThread.Element | null>(null);\n *\n * useAnimatedStyle(headerRef, scrollY, 'translateY', {\n * inputRange: [0, 300], outputRange: [0, -150], extrapolate: 'clamp',\n * });\n *\n * <ScrollView offsetY={scrollY}>\n * <view main-thread:ref={headerRef}><image src={hero} /></view>\n * <text>Body…</text>\n * </ScrollView>\n * ```\n *\n * @example BG-reactive scroll readout\n * ```tsx\n * const scrollY = useSharedValue(0);\n * <ScrollView offsetY={scrollY}>...</ScrollView>\n * <text>Scrolled: {scrollY.value.toFixed(0)}px</text>\n * ```\n */\nexport const ScrollView = component<ScrollViewProps>(({ props, slots }) => {\n // Always allocate fallback SharedValues — hooks must run unconditionally.\n // The render closure picks between own/external; the worklet always sees\n // a defined SharedValue in its `_c` capture.\n const ownX = useSharedValue(0);\n const ownY = useSharedValue(0);\n\n // Phase 2.12 ScrollView ↔ child-gesture coordination. Descendant\n // `<Draggable>` / `<Swipeable>` flip this signal during their drag so the\n // UIKit `panGestureRecognizer` (which doesn't participate in the new\n // gesture arena) yields the touch. See `scroll-context.ts` for the why.\n const dragging = signal(false);\n\n // Phase 2.13: publish the scroll-view's element ref through the context so\n // descendants can drive scroll directly from worklets (e.g. <Draggable\n // edgeScroll>). Captured at setup so the worklet `_c` map sees a stable\n // ref identity.\n const scrollViewRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Pick the axis SVs once; the same identity is shared with descendants via\n // the context (so they can read live scroll position) and used at render\n // time for the bindscroll worklet's `_c` capture.\n const x: SharedValue<number> = props.offsetX ?? ownX;\n const y: SharedValue<number> = props.offsetY ?? ownY;\n const scrollOrientation = props['scroll-orientation'] ?? 'vertical';\n\n defineProvide(useScrollContext, () => ({\n dragging,\n scrollViewRef,\n offsetX: x,\n offsetY: y,\n scrollOrientation,\n }));\n\n return () => {\n // Compose user-passed enable-scroll with the descendant-driven flag:\n // both must be true. User can still force-lock by passing `false`.\n const userEnableScroll = props['enable-scroll'] ?? true;\n const enableScroll = userEnableScroll && !dragging.value;\n return (\n <scroll-view\n main-thread:ref={scrollViewRef}\n scroll-orientation={scrollOrientation}\n enable-scroll={enableScroll}\n class={props.class}\n style={props.style}\n main-thread-bindscroll={(e: any) => {\n 'main thread';\n y.current.value = e.detail.scrollTop;\n x.current.value = e.detail.scrollLeft;\n // Apply useAnimatedStyle bindings on the same frame. Inlined\n // (rather than calling a helper) because plain function imports\n // don't survive worklet `_c` capture across the MT bundle —\n // same constraint @sigx/lynx-motion's `animate()` documents.\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n }}\n >\n {slots.default?.()}\n </scroll-view>\n );\n };\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/utils.ts","../src/use-pinch.ts","../src/use-rotation.ts","../src/components/Pressable.tsx","../src/scroll-context.ts","../src/components/Draggable.tsx","../src/components/Swipeable.tsx","../src/components/ScrollView.tsx"],"sourcesContent":["// Geometry helpers used by the multi-touch JS-only fallback hooks\n// (`usePinch`, `useRotation`). The arena-driven gesture surface\n// (`Gesture.*` + `useGestureDetector`) computes its own deltas natively;\n// this file is only relevant while the platform's pinch/rotation handlers\n// are unfinished.\n\nexport function distance(x1: number, y1: number, x2: number, y2: number): number {\n return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);\n}\n\nexport function midpoint(x1: number, y1: number, x2: number, y2: number): [number, number] {\n return [(x1 + x2) / 2, (y1 + y2) / 2];\n}\n\n/** Signed angle in radians from p1 to p2, range (-π, π]. */\nexport function angle(x1: number, y1: number, x2: number, y2: number): number {\n return Math.atan2(y2 - y1, x2 - x1);\n}\n\n/** Shortest signed angular delta between two radians, range (-π, π]. */\nexport function angleDelta(from: number, to: number): number {\n let d = to - from;\n while (d > Math.PI) d -= 2 * Math.PI;\n while (d <= -Math.PI) d += 2 * Math.PI;\n return d;\n}\n","import { signal } from '@sigx/lynx';\nimport type { UsePinchOptions, UsePinchReturn, TouchEvent, PinchState, TouchPoint } from './types';\nimport { distance, midpoint } from './utils';\n\n/**\n * Two-finger pinch/zoom gesture.\n *\n * Tracks fingers manually since Lynx fires separate touchstart events per\n * finger (each with touches.length=1). Uses proximity-based matching on\n * touchmove since Lynx identifiers are unreliable across events.\n *\n * NOTE: Requires a device/environment that delivers multi-touch events to\n * the same element. Some Lynx hosts (e.g. Lynx Explorer on emulator) may\n * not support this — test on a physical device.\n */\nexport function usePinch(options: UsePinchOptions = {}): UsePinchReturn {\n const { onPinch } = options;\n\n const state = signal<PinchState>({\n phase: 'idle',\n scale: 1,\n focalX: 0,\n focalY: 0,\n });\n\n let baseDistance = 0;\n let active = false;\n let finger1: TouchPoint | null = null;\n let finger2: TouchPoint | null = null;\n\n function onTouchStart(e: TouchEvent): void {\n const t = e.touches[0];\n if (!t) return;\n\n if (!finger1) {\n finger1 = { ...t };\n } else if (!finger2) {\n finger2 = { ...t };\n active = true;\n baseDistance = distance(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n state.phase = 'began';\n state.scale = 1;\n state.focalX = fx;\n state.focalY = fy;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n if (!active || !finger1 || !finger2) return;\n\n const t = e.changedTouches[0];\n if (!t) return;\n\n // Determine which finger moved by proximity\n const dist1 = distance(t.pageX, t.pageY, finger1.pageX, finger1.pageY);\n const dist2 = distance(t.pageX, t.pageY, finger2.pageX, finger2.pageY);\n\n if (dist1 < dist2) {\n finger1 = { ...t };\n } else {\n finger2 = { ...t };\n }\n\n const currentDist = distance(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const scale = baseDistance > 0 ? currentDist / baseDistance : 1;\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n\n state.phase = 'active';\n state.scale = scale;\n state.focalX = fx;\n state.focalY = fy;\n onPinch?.(state as PinchState);\n }\n\n function onTouchEnd(): void {\n if (active) {\n state.phase = 'ended';\n onPinch?.(state as PinchState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function onTouchCancel(): void {\n if (active) {\n state.phase = 'cancelled';\n onPinch?.(state as PinchState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function reset(): void {\n active = false;\n finger1 = null;\n finger2 = null;\n state.phase = 'idle';\n state.scale = 1;\n state.focalX = 0;\n state.focalY = 0;\n }\n\n return {\n state,\n handlers: {\n bindtouchstart: onTouchStart,\n bindtouchmove: onTouchMove,\n bindtouchend: onTouchEnd,\n bindtouchcancel: onTouchCancel,\n },\n reset,\n };\n}\n","import { signal } from '@sigx/lynx';\nimport type {\n UseRotationOptions,\n UseRotationReturn,\n TouchEvent,\n TouchPoint,\n RotationState,\n} from './types';\nimport { angle, angleDelta, distance, midpoint } from './utils';\n\n/**\n * Two-finger rotation gesture.\n *\n * Tracks the angle of the line between two fingers; reports cumulative rotation\n * in radians from gesture start. Like usePinch, uses proximity-based finger\n * matching on touchmove (Lynx touch identifiers are not stable across events).\n *\n * NOTE: requires multi-touch delivery to the same element. Some Lynx hosts\n * (Lynx Explorer on emulator) may not support this — test on a physical device.\n */\nexport function useRotation(options: UseRotationOptions = {}): UseRotationReturn {\n const { onRotation } = options;\n\n const state = signal<RotationState>({\n phase: 'idle',\n rotation: 0,\n velocity: 0,\n focalX: 0,\n focalY: 0,\n });\n\n let baseAngle = 0;\n let prevAngle = 0;\n let prevTime = 0;\n let active = false;\n let finger1: TouchPoint | null = null;\n let finger2: TouchPoint | null = null;\n\n function onTouchStart(e: TouchEvent): void {\n const t = e.touches[0];\n if (!t) return;\n\n if (!finger1) {\n finger1 = { ...t };\n } else if (!finger2) {\n finger2 = { ...t };\n active = true;\n baseAngle = angle(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n prevAngle = baseAngle;\n prevTime = Date.now();\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n state.phase = 'began';\n state.rotation = 0;\n state.velocity = 0;\n state.focalX = fx;\n state.focalY = fy;\n }\n }\n\n function onTouchMove(e: TouchEvent): void {\n if (!active || !finger1 || !finger2) return;\n const t = e.changedTouches[0];\n if (!t) return;\n\n const dist1 = distance(t.pageX, t.pageY, finger1.pageX, finger1.pageY);\n const dist2 = distance(t.pageX, t.pageY, finger2.pageX, finger2.pageY);\n if (dist1 < dist2) {\n finger1 = { ...t };\n } else {\n finger2 = { ...t };\n }\n\n const now = Date.now();\n const dt = Math.max(now - prevTime, 1);\n const currentAngle = angle(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n const rotation = angleDelta(baseAngle, currentAngle);\n const velocity = angleDelta(prevAngle, currentAngle) / dt;\n const [fx, fy] = midpoint(finger1.pageX, finger1.pageY, finger2.pageX, finger2.pageY);\n\n prevAngle = currentAngle;\n prevTime = now;\n\n state.phase = 'active';\n state.rotation = rotation;\n state.velocity = velocity;\n state.focalX = fx;\n state.focalY = fy;\n onRotation?.(state as RotationState);\n }\n\n function onTouchEnd(): void {\n if (active) {\n state.phase = 'ended';\n onRotation?.(state as RotationState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function onTouchCancel(): void {\n if (active) {\n state.phase = 'cancelled';\n onRotation?.(state as RotationState);\n }\n active = false;\n finger1 = null;\n finger2 = null;\n }\n\n function reset(): void {\n active = false;\n finger1 = null;\n finger2 = null;\n state.phase = 'idle';\n state.rotation = 0;\n state.velocity = 0;\n state.focalX = 0;\n state.focalY = 0;\n }\n\n return {\n state,\n handlers: {\n bindtouchstart: onTouchStart,\n bindtouchmove: onTouchMove,\n bindtouchend: onTouchEnd,\n bindtouchcancel: onTouchCancel,\n },\n reset,\n };\n}\n","import {\n component,\n useMainThreadRef,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\n\nexport type PressableProps =\n & Define.Prop<'pressedOpacity', number, false>\n & Define.Prop<'pressedScale', number, false>\n & Define.Prop<'longPressDuration', number, false>\n & Define.Prop<'maxDistance', number, false>\n & Define.Prop<'disabled', boolean, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'press', void>\n & Define.Event<'longPress', void>;\n\ninterface PressableMTState {\n longPressFired: boolean;\n pressEmitted: boolean;\n startPageX: number;\n startPageY: number;\n}\n\n/**\n * MT-thread tap + long-press recognizer with built-in pressed-state visual\n * feedback (opacity + scale). Press and long-press callbacks are dispatched\n * to BG via `runOnBackground` (low-frequency cross-thread is fine).\n *\n * Cross-platform gesture-arena quirks (Phase 2.12.1, observed on iOS Lynx\n * 3.5 sim and Android Lynx 3.6 / Pixel 9 Pro XL) make this component a\n * hybrid: it composes `Gesture.Tap()` + `Gesture.LongPress()` via\n * `Simultaneous` AND adds an onEnd-fallback path inside LongPress, so press\n * emission works on both platforms via different routes:\n *\n * - **Android**: `Tap.onStart` fires on touch-up (as documented). Press\n * emits there; the LongPress fallback sees `pressEmitted=true` and\n * skips. `Tap.onEnd` fires on the same touch-up — but iOS's premature\n * onEnd (next bullet) means we can't safely reset styles here, so style\n * reset lives in LongPress.onEnd.\n * - **iOS**: `Tap.onEnd` fires ~6ms after touchstart (an arena\n * fail/reset path that doesn't trigger on Android). `Tap.onStart`\n * never fires for our composition. We rely on `LongPress.onEnd` to\n * detect \"lift before duration with no movement\" and emit press from\n * the fallback. `Gesture.Race` would be simpler in theory, but its\n * `waitFor` deadlocks Tap on iOS — the arena dispatches Tap before\n * LongPress reaches Fail state.\n *\n * State tracks `longPressFired` and `pressEmitted` so neither event\n * double-fires regardless of which platform path resolves first.\n * Movement past `maxDistance` is tracked from `e.params.pageX/pageY`;\n * `LongPress.onEnd` skips press emission when the touch drifted past\n * the threshold (matching Tap's success criteria).\n *\n * Disabled is captured at setup; runtime toggling won't update an active\n * gesture's behavior. Wrap the parent in conditional rendering for now if\n * dynamic disable is needed.\n */\nexport const Pressable = component<PressableProps>(({ props, slots, emit }) => {\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n const opacity = props.pressedOpacity ?? 0.6;\n const scale = props.pressedScale ?? 1;\n // longPressDuration === 0 disables long-press: we set minDuration to a\n // huge value so the platform timer never fires; the iOS press fallback\n // path still works because it's gated on `!longPressFired` (which stays\n // false), and on Android the Tap.onStart path is unaffected.\n const longPressDuration = props.longPressDuration ?? 500;\n const minDuration = longPressDuration > 0 ? longPressDuration : 1_000_000;\n const maxDistance = props.maxDistance ?? 10;\n const maxDistanceSq = maxDistance * maxDistance;\n const disabled = props.disabled ?? false;\n\n const state = useMainThreadRef<PressableMTState>({\n longPressFired: false,\n pressEmitted: false,\n startPageX: 0,\n startPageY: 0,\n });\n\n const tap = Gesture.Tap()\n .maxDistance(maxDistance)\n .onBegin((e: any) => {\n 'main thread';\n if (disabled) return;\n // Reset the cross-platform state on every fresh touch-down. Both\n // Tap.onBegin and LongPress.onBegin fire — first one wins, second\n // is a no-op because pressEmitted/longPressFired are already false.\n state.current.longPressFired = false;\n state.current.pressEmitted = false;\n const p = e && e.params;\n state.current.startPageX = (p && p.pageX) || 0;\n state.current.startPageY = (p && p.pageY) || 0;\n elRef.current?.setStyleProperties({\n opacity: opacity,\n transform: 'scale(' + scale + ')',\n });\n })\n .onStart(() => {\n 'main thread';\n if (disabled) return;\n // Android path: Tap.onStart fires on touchend within maxDuration;\n // emit press here. The LongPress.onEnd fallback below is gated on\n // !pressEmitted so it won't double-fire on Android.\n if (!state.current.pressEmitted) {\n state.current.pressEmitted = true;\n runOnBackground(() => { emit('press'); })();\n }\n });\n // No Tap.onEnd: iOS fires it ~6ms after touchstart (arena fail/reset\n // path), which would prematurely reset our press-state styles. Style\n // reset lives in LongPress.onEnd, which fires only on real touch-up.\n\n // Always pair Tap with a LongPress gesture — even when long-press is\n // \"disabled\" by the consumer. LongPress.onEnd is the reliable terminal\n // hook that resets the pressed visual state on touch-up across iOS +\n // Android; without it the child would stay stuck in the pressed style.\n // We disable long-press semantically (never fires) by pushing its\n // minDuration past any realistic touch by setting it to MAX_SAFE_INTEGER\n // while keeping the gesture registered for its onEnd lifecycle.\n const longPressEnabled = longPressDuration > 0;\n const longPress = Gesture.LongPress()\n .minDuration(longPressEnabled ? minDuration : Number.MAX_SAFE_INTEGER)\n .maxDistance(maxDistance)\n .onBegin(() => {\n 'main thread';\n if (disabled) return;\n // Idempotent with Tap.onBegin — both fire on touch-down. State has\n // already been initialised by Tap.onBegin (whichever fires first).\n elRef.current?.setStyleProperties({\n opacity: opacity,\n transform: 'scale(' + scale + ')',\n });\n })\n .onStart(() => {\n 'main thread';\n if (disabled) return;\n state.current.longPressFired = true;\n runOnBackground(() => { emit('longPress'); })();\n })\n .onEnd((e: any) => {\n 'main thread';\n // Reset visual feedback regardless of how this terminal state was\n // reached (success / fail / cancel / lift-before-duration).\n elRef.current?.setStyleProperties({\n opacity: 1,\n transform: 'scale(1)',\n });\n if (disabled) return;\n // iOS fallback path. On iOS Tap.onStart never fires, so press would\n // never emit without this. On Android this is a no-op because\n // pressEmitted is already true (or longPressFired is true).\n if (state.current.longPressFired || state.current.pressEmitted) return;\n const p = e && e.params;\n if (!p) return;\n const dx = (p.pageX || 0) - state.current.startPageX;\n const dy = (p.pageY || 0) - state.current.startPageY;\n if (dx * dx + dy * dy > maxDistanceSq) return; // movement-cancel\n state.current.pressEmitted = true;\n runOnBackground(() => { emit('press'); })();\n });\n\n const gesture = Gesture.Simultaneous(tap, longPress);\n\n useGestureDetector(elRef, gesture);\n\n return () => (\n <view\n class={props.class}\n style={props.style}\n main-thread:ref={elRef}\n >\n {slots.default?.()}\n </view>\n );\n});\n","import {\n defineInjectable,\n type PrimitiveSignal,\n type SharedValue,\n type MainThreadRef,\n type MainThread,\n} from '@sigx/lynx';\n\n/**\n * Scroll-arena coordination context, provided by `<ScrollView>` and consumed\n * by descendant gesture components (`<Draggable>`, `<Swipeable>`).\n *\n * Why this exists: Lynx's `<scroll-view>` does NOT participate in the new\n * gesture arena (`LynxGestureArenaManager`) on iOS — its UIKit\n * `panGestureRecognizer` runs independently of arena gestures, so a Pan\n * registered on a descendant element fires concurrently with the parent\n * scroll. The visible result is \"drag works but the page scrolls too,\n * sliding the box away from the finger\".\n *\n * Workaround: the parent `<ScrollView>` exposes a BG-side `dragging` signal\n * that gates its `enable-scroll` prop. Gesture children flip the signal\n * during their lifecycle (onStart/onEnd → onDragStart/onDragEnd) so the\n * UIScrollView pan recognizer is disabled while a child gesture owns the\n * touch.\n *\n * This is a Phase 2.12 framework-level encapsulation of what consumers had\n * to wire by hand in Phase 2.11. A proper fix lives on the Lynx native\n * side: making `<scroll-view>`'s pan recognizer participate in the arena\n * (or yielding to arena recognizers in `shouldBeRequiredToFailByGestureRecognizer:`).\n * Until then, this is the cleanest the framework can be.\n *\n * Returns `null` when no parent `<ScrollView>` is in scope, so consumers\n * branch on presence:\n *\n * ```ts\n * const scrollCtx = useScrollContext();\n * // ... inside an onStart's runOnBackground arrow:\n * if (scrollCtx) scrollCtx.dragging.value = true;\n * ```\n *\n * Phase 2.13 extends the context with the scroll-view's element ref + live\n * scroll-position SVs + axis, so descendants can drive scroll directly\n * (edge-scroll while dragging, etc.) without re-piping refs through props.\n */\nexport interface ScrollContext {\n /** BG-side flag the parent `<ScrollView>` reads as `enable-scroll={!dragging.value}`. */\n dragging: PrimitiveSignal<boolean>;\n /**\n * MT element ref to the underlying `<scroll-view>`. Null until mounted.\n * Descendants call `scrollViewRef.current?.invoke('scrollBy', ...)` from\n * worklets to drive scroll programmatically.\n */\n scrollViewRef: MainThreadRef<MainThread.Element | null>;\n /**\n * Live horizontal scroll position. Same SV the consumer passes via\n * `<ScrollView offsetX={…}>` (or an internally-allocated fallback).\n */\n offsetX: SharedValue<number>;\n /**\n * Live vertical scroll position. Same SV the consumer passes via\n * `<ScrollView offsetY={…}>` (or an internally-allocated fallback).\n */\n offsetY: SharedValue<number>;\n /**\n * Scroll axis as configured on the `<scroll-view>`. Edge-scroll\n * descendants pick which edges to monitor based on this:\n * `'vertical'` → top/bottom; `'horizontal'` → left/right.\n */\n scrollOrientation: 'vertical' | 'horizontal';\n}\n\nexport const useScrollContext = defineInjectable<ScrollContext | null>(() => null);\n","import {\n component,\n useMainThreadRef,\n useSharedValue,\n useAnimatedStyle,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type SharedValue,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context';\n\nexport interface DragEndDetail {\n x: number;\n y: number;\n vx: number;\n vy: number;\n}\n\n/**\n * Edge-scroll configuration for `<Draggable edgeScroll>`. Either `true` for\n * default tuning, or an object overriding the defaults.\n */\nexport type EdgeScrollConfig = boolean | {\n /** Distance from viewport edge in pt where auto-scroll engages. Default 50. */\n threshold?: number;\n /** Maximum scroll velocity in pt/sec at the edge. Default 800. */\n maxSpeed?: number;\n};\n\nexport type DraggableProps =\n & Define.Prop<'axis', 'x' | 'y' | 'both', false>\n & Define.Prop<'threshold', number, false>\n & Define.Prop<'snapBack', boolean, false>\n & Define.Prop<'minX', number, false>\n & Define.Prop<'maxX', number, false>\n & Define.Prop<'minY', number, false>\n & Define.Prop<'maxY', number, false>\n & Define.Prop<'translateX', SharedValue<number>, false>\n & Define.Prop<'translateY', SharedValue<number>, false>\n & Define.Prop<'edgeScroll', EdgeScrollConfig, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'dragStart', { x: number; y: number }>\n & Define.Event<'dragEnd', DragEndDetail>;\n\ninterface DragMTState {\n startPageX: number;\n startPageY: number;\n offsetX: number;\n offsetY: number;\n prevPageX: number;\n prevPageY: number;\n prevTime: number;\n vx: number;\n vy: number;\n // Phase 2.13 edge-scroll state. Populated lazily in onStart when edgeScroll\n // is enabled and a parent ScrollView is in scope. Read by the rAF tick\n // closure scheduled from onUpdate.\n lastPageX: number;\n lastPageY: number;\n scrollViewLeft: number;\n scrollViewTop: number;\n scrollViewWidth: number;\n scrollViewHeight: number;\n edgeScrollActive: boolean;\n /**\n * Last observed parent ScrollView offset, sampled inside the rAF tick.\n * Used to compute the *actual* scroll delta between frames so the\n * compensation matches what the native scroll-view delivered (zero when\n * it clamped at top/bottom). Without this, holding past the top edge\n * keeps adding negative delta to ty even though the page can't scroll\n * any further, drifting the box off-screen.\n */\n lastScrollX: number;\n lastScrollY: number;\n}\n\n/**\n * MT-thread draggable container, built on the native gesture arena via\n * `Gesture.Pan()`. The bound element's transform is driven by two\n * `useAnimatedStyle` bindings (one per axis) — the same primitive any user\n * could compose. The Pan onUpdate worklet writes to the SharedValues; the\n * bridge applies the transform on the next flush boundary, composing the\n * two bindings into a single `setStyleProperties({ transform })` call.\n *\n * Because the visible position is bridge-driven rather than written directly\n * by the worklet, external animation of `translateX`/`translateY` (e.g.\n * `withSpring(tx, 0)` to spring back to origin after release) moves the\n * element visually for free — the binding picks up whichever SV write\n * happened most recently, regardless of who wrote it.\n *\n * `dragStart` and `dragEnd` are dispatched to BG via `runOnBackground` (low\n * frequency, cross-thread is fine).\n *\n * Unlike the prior `bindtouch*`-based implementation, the native pan gesture\n * arena handles multi-touch correctly (secondary fingers don't cancel the\n * primary drag).\n *\n * **Scroll composition** (Phase 2.12.3): Lynx's `<scroll-view>` doesn't\n * participate in the new gesture arena, so without coordination both pan\n * and scroll would fire concurrently. `<Draggable>` reads `useScrollContext`\n * at setup; if a parent `<ScrollView>` is in scope, the BG-side dragStart/\n * dragEnd flips `scrollCtx.dragging` automatically — the parent's\n * `enable-scroll` is gated on that signal, so the UIKit pan recognizer\n * yields for the duration of the drag. No consumer wiring required.\n *\n * **Edge-scroll** (Phase 2.13): pass `edgeScroll` to auto-scroll the parent\n * `<ScrollView>` when the finger nears its viewport edge during a drag —\n * the standard drag-to-reorder pattern (Apple Mail, iOS Reminders). The\n * scroll axis follows `scrollOrientation` as published through the context.\n * Inside the threshold zone the scroll velocity ramps from 0 at the\n * threshold boundary to `maxSpeed` at the edge. Quietly no-ops if\n * `edgeScroll` is unset OR the Draggable isn't nested in a ScrollView.\n *\n * Note on the native event payload: Lynx's pan handler emits `pageX`/`pageY`\n * but no `translationX`/`velocityX` — we compute deltas and velocity from\n * pageX/pageY ourselves (same as the prior touch-based implementation).\n */\nexport const Draggable = component<DraggableProps>(({ props, slots, emit }) => {\n const elRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Always allocate fallback SharedValues — hooks must run unconditionally.\n const ownTx = useSharedValue(0);\n const ownTy = useSharedValue(0);\n\n // Pick once at setup so useAnimatedStyle bindings + worklet `_c` captures\n // hold stable refs. Convention (also used by <ScrollView>): gesture-prop\n // SVs are allocated once at the parent and don't swap across renders.\n const tx = props.translateX ?? ownTx;\n const ty = props.translateY ?? ownTy;\n\n // Bridge tx/ty → element transform on every flush boundary. Composes with\n // any external animation that mutates the same SVs.\n useAnimatedStyle(elRef, tx, 'translateX');\n useAnimatedStyle(elRef, ty, 'translateY');\n\n const drag = useMainThreadRef<DragMTState>({\n startPageX: 0, startPageY: 0,\n offsetX: 0, offsetY: 0,\n prevPageX: 0, prevPageY: 0, prevTime: 0,\n vx: 0, vy: 0,\n lastPageX: 0, lastPageY: 0,\n scrollViewLeft: 0, scrollViewTop: 0,\n scrollViewWidth: 0, scrollViewHeight: 0,\n edgeScrollActive: false,\n lastScrollX: 0, lastScrollY: 0,\n });\n\n // Coordinate with the parent <ScrollView> (Phase 2.12.3): toggle its\n // dragging signal during our gesture so its UIScrollView pan recognizer\n // yields. Null when no ancestor ScrollView; the BG arrows below null-check.\n const scrollCtx = useScrollContext();\n\n // Pan config is read once at setup. Worklet bodies capture the snapshot\n // via SWC's `_c` mechanism; runtime prop changes won't update an active\n // gesture, but axis/threshold/clamps are render-stable in practice.\n const axis = props.axis ?? 'both';\n const threshold = props.threshold ?? 0;\n const snapBack = props.snapBack ?? false;\n const minX = props.minX;\n const maxX = props.maxX;\n const minY = props.minY;\n const maxY = props.maxY;\n\n // Phase 2.13: edge-scroll config. Normalized to plain numbers/booleans so\n // worklet `_c` captures stay shape-stable. `edgeScrollEnabled` gates the\n // viewport-measurement and rAF-tick paths in onStart/onUpdate; falsey\n // means the new code paths short-circuit and behave identically to the\n // pre-2.13 Draggable. `??` (not `||`) so an explicit `0` override for the\n // edge zone or speed cap is preserved.\n const edgeScrollProp = props.edgeScroll ?? false;\n const edgeScrollEnabled = edgeScrollProp !== false;\n const edgeScrollThreshold =\n (typeof edgeScrollProp === 'object' ? edgeScrollProp.threshold : undefined) ?? 50;\n const edgeScrollMaxSpeed =\n (typeof edgeScrollProp === 'object' ? edgeScrollProp.maxSpeed : undefined) ?? 800;\n // Captured at setup so the onUpdate tick reads a stable axis. `<ScrollView>`\n // captures `scroll-orientation` once at setup too, so this stays consistent.\n const scrollOrientation: 'vertical' | 'horizontal' = scrollCtx?.scrollOrientation ?? 'vertical';\n\n const pan = Gesture.Pan()\n .minDistance(threshold)\n // Empty onBegin is load-bearing on iOS: LynxPanGestureHandler bails out\n // of onStart/onEnd unless `_isInvokedBegin` is YES, and that flag is\n // only set inside the native onBegin handler — which itself short-\n // circuits when no callback is registered. Registering any onBegin\n // (even a no-op) gates the begin path open so onStart and onEnd fire.\n .onBegin(() => {\n 'main thread';\n })\n .onStart((e: any) => {\n 'main thread';\n // Pan event payload: { type, timestamp, target, currentTarget,\n // params: { pageX, pageY, x, y, clientX, clientY,\n // scrollX, scrollY, isAtStart, isAtEnd,\n // type } , detail: <copy of params> }\n // pageX/pageY are nested under params; the top-level event has only\n // dispatch metadata.\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const pageY = (p && p.pageY) || 0;\n drag.current.startPageX = pageX;\n drag.current.startPageY = pageY;\n drag.current.offsetX = tx.current.value;\n drag.current.offsetY = ty.current.value;\n drag.current.prevPageX = pageX;\n drag.current.prevPageY = pageY;\n drag.current.prevTime = Date.now();\n drag.current.vx = 0;\n drag.current.vy = 0;\n drag.current.lastPageX = pageX;\n drag.current.lastPageY = pageY;\n drag.current.edgeScrollActive = false;\n // Lazy viewport measurement for edge-scroll. Reads computed size of\n // the parent <scroll-view> via the ref the context publishes; assumes\n // page-rooted (top-left at page origin) which holds for the showcase\n // and the standard \"list takes the whole screen\" pattern. Refine via\n // a `boundingClientRect` invoke (returns Promise — needs async-stash)\n // if a nested-scroll-view consumer hits it.\n if (edgeScrollEnabled && scrollCtx) {\n const svRef = scrollCtx.scrollViewRef.current;\n if (svRef) {\n // Use `boundingClientRect` over `getComputedStyleProperty`: it\n // returns page-relative geometry that's consistent across iOS +\n // Android. `getComputedStyleProperty('height')` sometimes returns\n // unresolved `100vh`-style strings or content heights on Android,\n // which made the bottom-edge zone unreachable on Pixel.\n //\n // The invoke is async (Promise-based) — the rect lands a tick or\n // two after this call, so the first few onUpdate frames may see\n // scrollView{Width,Height}=0 and skip the rAF schedule. By the\n // time the user has dragged anywhere meaningful, the rect is\n // populated and edge-scroll engages.\n const rectP = svRef.invoke('boundingClientRect', {});\n if (rectP && typeof rectP.then === 'function') {\n rectP.then((rect: unknown) => {\n if (!rect || typeof rect !== 'object') return;\n const r = rect as { left?: number; top?: number; width?: number; height?: number };\n drag.current.scrollViewLeft = r.left || 0;\n drag.current.scrollViewTop = r.top || 0;\n drag.current.scrollViewWidth = r.width || 0;\n drag.current.scrollViewHeight = r.height || 0;\n }).catch(() => {});\n }\n }\n // Seed last-known scroll offsets so the first tick's \"actual delta\"\n // baselines correctly. After the first frame, the rAF tick keeps\n // these in sync with the live offsetX/Y SVs.\n drag.current.lastScrollX = scrollCtx.offsetX.current.value;\n drag.current.lastScrollY = scrollCtx.offsetY.current.value;\n }\n runOnBackground((startX: number, startY: number) => {\n if (scrollCtx) scrollCtx.dragging.value = true;\n emit('dragStart', { x: startX, y: startY });\n })(tx.current.value, ty.current.value);\n })\n .onUpdate((e: any) => {\n 'main thread';\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n const pageY = (p && p.pageY) || 0;\n let dx = pageX - drag.current.startPageX;\n let dy = pageY - drag.current.startPageY;\n if (axis === 'x') dy = 0;\n else if (axis === 'y') dx = 0;\n let newX = drag.current.offsetX + dx;\n let newY = drag.current.offsetY + dy;\n if (minX !== undefined && newX < minX) newX = minX;\n if (maxX !== undefined && newX > maxX) newX = maxX;\n if (minY !== undefined && newY < minY) newY = minY;\n if (maxY !== undefined && newY > maxY) newY = maxY;\n const now = Date.now();\n const dt = Math.max(now - drag.current.prevTime, 1);\n drag.current.vx = (pageX - drag.current.prevPageX) / dt;\n drag.current.vy = (pageY - drag.current.prevPageY) / dt;\n drag.current.prevPageX = pageX;\n drag.current.prevPageY = pageY;\n drag.current.prevTime = now;\n drag.current.lastPageX = pageX;\n drag.current.lastPageY = pageY;\n tx.current.value = newX;\n ty.current.value = newY;\n // Drive the useAnimatedStyle bindings on the same frame. Inlined\n // (rather than calling an imported helper) because plain function\n // imports don't survive worklet `_c` capture — same constraint as\n // <ScrollView> and @sigx/lynx-motion's animate().\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n // Phase 2.13 edge-scroll: enter the rAF loop when the finger crosses\n // into the threshold zone. The tick closure self-cancels when\n // `edgeScrollActive` flips false (onEnd) or the finger leaves the\n // zone (zero velocity).\n if (edgeScrollEnabled && scrollCtx && !drag.current.edgeScrollActive) {\n const w = drag.current.scrollViewWidth;\n const h = drag.current.scrollViewHeight;\n if (w > 0 && h > 0) {\n let inEdge = false;\n if (scrollOrientation === 'vertical') {\n const top = drag.current.scrollViewTop;\n const py = drag.current.lastPageY;\n const topDist = py - top;\n const botDist = (top + h) - py;\n inEdge = topDist < edgeScrollThreshold || botDist < edgeScrollThreshold;\n } else {\n const left = drag.current.scrollViewLeft;\n const px = drag.current.lastPageX;\n const leftDist = px - left;\n const rightDist = (left + w) - px;\n inEdge = leftDist < edgeScrollThreshold || rightDist < edgeScrollThreshold;\n }\n if (inEdge) {\n drag.current.edgeScrollActive = true;\n // Inner arrow runs on MT (we're already inside an MT worklet\n // body); rAF stashes the closure across frames in the MT VM.\n // No `'main thread'` directive needed — the directive marks\n // function bodies that cross threads, and we never leave MT.\n const tick = (): void => {\n if (!drag.current.edgeScrollActive) return;\n const ref = scrollCtx.scrollViewRef.current;\n if (!ref) {\n drag.current.edgeScrollActive = false;\n return;\n }\n let velocity = 0;\n if (scrollOrientation === 'vertical') {\n const py2 = drag.current.lastPageY;\n const top2 = drag.current.scrollViewTop;\n const h2 = drag.current.scrollViewHeight;\n const topDist2 = py2 - top2;\n const botDist2 = (top2 + h2) - py2;\n if (topDist2 < edgeScrollThreshold) {\n const t = topDist2 < 0 ? 0 : topDist2;\n velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n } else if (botDist2 < edgeScrollThreshold) {\n const t = botDist2 < 0 ? 0 : botDist2;\n velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n }\n } else {\n const px2 = drag.current.lastPageX;\n const left2 = drag.current.scrollViewLeft;\n const w2 = drag.current.scrollViewWidth;\n const leftDist2 = px2 - left2;\n const rightDist2 = (left2 + w2) - px2;\n if (leftDist2 < edgeScrollThreshold) {\n const t = leftDist2 < 0 ? 0 : leftDist2;\n velocity = -edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n } else if (rightDist2 < edgeScrollThreshold) {\n const t = rightDist2 < 0 ? 0 : rightDist2;\n velocity = edgeScrollMaxSpeed * (1 - t / edgeScrollThreshold);\n }\n }\n if (velocity === 0) {\n drag.current.edgeScrollActive = false;\n return;\n }\n // Scroll-delta compensation: when content scrolls by `delta`,\n // the Draggable's layout position moves by `-delta` (it's a\n // child of the content). Without compensation the box drifts\n // away from the finger as the page scrolls.\n //\n // We use the *actual* delivered scroll delta (read from\n // offsetX/Y the bindscroll worklet maintains), not the\n // velocity-based request. When the scroll-view clamps at the\n // top/bottom (already at the edge), the actual delta is zero\n // and we skip compensation — otherwise the box would keep\n // drifting off-screen as we issue scrollBy calls the native\n // side rejects.\n //\n // There's a one-frame lag (this tick reads the previous\n // frame's actual delta), but it's imperceptible at 60fps.\n const currScrollX = scrollCtx.offsetX.current.value;\n const currScrollY = scrollCtx.offsetY.current.value;\n const actualDX = currScrollX - drag.current.lastScrollX;\n const actualDY = currScrollY - drag.current.lastScrollY;\n drag.current.lastScrollX = currScrollX;\n drag.current.lastScrollY = currScrollY;\n if (scrollOrientation === 'vertical') {\n if (actualDY !== 0) {\n drag.current.offsetY += actualDY;\n ty.current.value += actualDY;\n }\n } else {\n if (actualDX !== 0) {\n drag.current.offsetX += actualDX;\n tx.current.value += actualDX;\n }\n }\n // 60 fps tick → offset (pt/frame) = velocity (pt/sec) / 60.\n // scrollBy on a vertical scroll-view ignores the X component\n // of the offset (and vice-versa per\n // LynxUIScrollViewInternal.m:269), so a single signed\n // `offset` works for both axes.\n //\n // `invoke()` already calls `__FlushElementTree()` internally\n // (`MTElementWrapper.invoke` sequences `__InvokeUIMethod` →\n // `__FlushElementTree` synchronously inside the Promise\n // constructor), which picks up the SV write above. No\n // explicit flush needed — adding one would double the\n // per-frame work and contributes to scroll stutter.\n const delta = velocity / 60;\n const p2 = ref.invoke('scrollBy', { offset: delta });\n if (p2 && typeof p2.catch === 'function') p2.catch(() => {});\n const raf = (globalThis as Record<string, unknown>)['requestAnimationFrame'] as\n ((cb: () => void) => void) | undefined;\n if (raf) raf(tick);\n else drag.current.edgeScrollActive = false;\n };\n const raf = (globalThis as Record<string, unknown>)['requestAnimationFrame'] as\n ((cb: () => void) => void) | undefined;\n if (raf) raf(tick);\n else drag.current.edgeScrollActive = false;\n }\n }\n }\n })\n .onEnd(() => {\n 'main thread';\n // Stop the edge-scroll rAF loop (if any). The tick self-cancels next\n // frame on the flag flip.\n drag.current.edgeScrollActive = false;\n if (snapBack) {\n tx.current.value = 0;\n ty.current.value = 0;\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n }\n // Capture MT values into locals before crossing back to BG —\n // `tx.current.value` on the BG side reads the initial snapshot, not\n // the live drag position. Same goes for `drag.current.vx/vy`.\n const endX = tx.current.value;\n const endY = ty.current.value;\n const endVx = drag.current.vx;\n const endVy = drag.current.vy;\n runOnBackground((x: number, y: number, vx: number, vy: number) => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('dragEnd', { x, y, vx, vy });\n })(endX, endY, endVx, endVy);\n });\n\n useGestureDetector(elRef, pan);\n\n return () => (\n <view\n class={props.class}\n style={props.style}\n main-thread:ref={elRef}\n >\n {slots.default?.()}\n </view>\n );\n});\n","import {\n component,\n useMainThreadRef,\n useSharedValue,\n useAnimatedStyle,\n runOnBackground,\n Gesture,\n useGestureDetector,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context';\n\nexport type SwipeSide = 'left' | 'right';\n\nexport type SwipeableProps =\n & Define.Prop<'leftActionsWidth', number, false>\n & Define.Prop<'rightActionsWidth', number, false>\n & Define.Prop<'snapThreshold', number, false>\n & Define.Prop<'snapDuration', number, false>\n & Define.Prop<'leftActions', () => unknown, false>\n & Define.Prop<'rightActions', () => unknown, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Prop<'foregroundStyle', Record<string, string | number>, false>\n & Define.Slot<'default'>\n & Define.Event<'swipeOpen', { side: SwipeSide }>\n & Define.Event<'swipeClose', void>;\n\ninterface SwipeMTState {\n startPageX: number;\n offsetX: number;\n /** Snapped resting position: 0, +leftWidth, or -rightWidth. */\n currentX: number;\n}\n\n/**\n * Horizontal swipe-to-reveal container, built on the native gesture arena\n * via `Gesture.Pan().axis('x')`. The foreground is dragged horizontally on\n * the MT thread; on release it snaps to one of three resting positions\n * (closed / open-left / open-right) using `MTElementWrapper.animate()`.\n * Open and close events are dispatched to BG via `runOnBackground`.\n *\n * Migrated from a 4-`bindtouch*`-worklet implementation to a single\n * `Gesture.Pan()` (Phase 2.12). Carries the same Phase 2.11 quirks:\n * - `.onBegin(() => {})` no-op is load-bearing on iOS Pan to gate\n * `_isInvokedBegin` open so onStart/onEnd fire.\n * - `e.params.pageX` (not `e.pageX`) — Lynx pan event nests the touch\n * payload under `params`.\n *\n * Supply `leftActions` and/or `rightActions` as render-prop functions:\n *\n * ```tsx\n * <Swipeable\n * rightActions={() => <view><text>Delete</text></view>}\n * onSwipeOpen={(e) => console.log('opened', e.side)}\n * >\n * <view><text>Row content</text></view>\n * </Swipeable>\n * ```\n *\n * **Scroll composition** (Phase 2.12.3): nesting `<Swipeable>` inside\n * `<ScrollView>` is automatic — `useScrollContext` is read at setup and\n * the BG-side onStart/onEnd handlers flip `scrollCtx.dragging` so the\n * parent yields its UIKit pan for the duration of the swipe. No consumer\n * wiring required.\n */\nexport const Swipeable = component<SwipeableProps>(({ props, slots, emit }) => {\n const fgRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Drive the foreground transform via a SharedValue so external animations\n // could compose if we ever wanted spring snaps. For now we still call\n // `.animate()` on the element directly for the snap; the SV is only the\n // intermediate write target during the drag.\n const tx = useSharedValue(0);\n useAnimatedStyle(fgRef, tx, 'translateX');\n\n const drag = useMainThreadRef<SwipeMTState>({\n startPageX: 0,\n offsetX: 0,\n currentX: 0,\n });\n\n // Coordinate with the parent <ScrollView> (Phase 2.12.3) — see Draggable\n // for the why. Null when no ancestor ScrollView.\n const scrollCtx = useScrollContext();\n\n const leftWidth = props.leftActionsWidth ?? 100;\n const rightWidth = props.rightActionsWidth ?? 100;\n const snapThreshold = props.snapThreshold ?? 60;\n const snapDuration = props.snapDuration ?? 200;\n const hasLeft = !!props.leftActions;\n const hasRight = !!props.rightActions;\n const upper = hasLeft ? leftWidth : 0;\n const lower = hasRight ? -rightWidth : 0;\n\n const pan = Gesture.Pan()\n .axis('x')\n // Empty onBegin gates `_isInvokedBegin` open on iOS so onStart/onEnd fire.\n .onBegin(() => {\n 'main thread';\n })\n .onStart((e: any) => {\n 'main thread';\n const p = e && e.params;\n drag.current.startPageX = (p && p.pageX) || 0;\n drag.current.offsetX = drag.current.currentX;\n // Tell the parent ScrollView (if any) we own the touch.\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = true;\n })();\n })\n .onUpdate((e: any) => {\n 'main thread';\n const p = e && e.params;\n const pageX = (p && p.pageX) || 0;\n let x = drag.current.offsetX + (pageX - drag.current.startPageX);\n if (x > upper) x = upper;\n if (x < lower) x = lower;\n tx.current.value = x;\n // Bridge the binding on the same frame so the foreground tracks the\n // finger without a vsync delay (same trick as Draggable).\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n })\n .onEnd(() => {\n 'main thread';\n const x = tx.current.value;\n // Snap to closest resting position.\n let target = 0;\n if (hasLeft && x > snapThreshold) target = leftWidth;\n else if (hasRight && x < -snapThreshold) target = -rightWidth;\n fgRef.current?.animate(\n [\n { transform: 'translateX(' + x + 'px)' },\n { transform: 'translateX(' + target + 'px)' },\n ],\n { duration: snapDuration, fill: 'forwards', easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' },\n )?.play();\n // Keep the SV in sync with the snap target so subsequent drags don't\n // jump back to the pre-animate position.\n tx.current.value = target;\n const wasOpen = drag.current.currentX !== 0;\n const nowOpen = target !== 0;\n drag.current.currentX = target;\n // Always release the parent ScrollView's claim, regardless of snap.\n // Bundled into the same runOnBackground call as the emit so we only\n // pay one cross-thread hop.\n if (nowOpen) {\n const side: SwipeSide = target > 0 ? 'left' : 'right';\n runOnBackground((s: SwipeSide) => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('swipeOpen', { side: s });\n })(side);\n } else if (wasOpen) {\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n emit('swipeClose');\n })();\n } else {\n // Closed→closed: still need to release the ScrollView claim.\n runOnBackground(() => {\n if (scrollCtx) scrollCtx.dragging.value = false;\n })();\n }\n });\n\n useGestureDetector(fgRef, pan);\n\n return () => (\n <view\n class={props.class}\n style={{\n position: 'relative',\n overflow: 'hidden',\n ...(props.style || {}),\n }}\n >\n {hasLeft ? (\n <view style={{\n position: 'absolute',\n left: '0',\n top: '0',\n bottom: '0',\n width: leftWidth + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n {props.leftActions!()}\n </view>\n ) : null}\n\n {hasRight ? (\n <view style={{\n position: 'absolute',\n right: '0',\n top: '0',\n bottom: '0',\n width: rightWidth + 'px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}>\n {props.rightActions!()}\n </view>\n ) : null}\n\n <view\n main-thread:ref={fgRef}\n style={{\n position: 'relative',\n ...(props.foregroundStyle || {}),\n }}\n >\n {slots.default?.()}\n </view>\n </view>\n );\n});\n","import {\n component,\n signal,\n useSharedValue,\n useMainThreadRef,\n defineProvide,\n type SharedValue,\n type Define,\n type MainThread,\n} from '@sigx/lynx';\nimport { useScrollContext } from '../scroll-context';\n\nexport type ScrollViewProps =\n & Define.Prop<'offsetX', SharedValue<number>, false>\n & Define.Prop<'offsetY', SharedValue<number>, false>\n & Define.Prop<'scroll-orientation', 'vertical' | 'horizontal', false>\n /**\n * Toggle native scroll responsiveness at runtime — set false to lock the\n * scroll-view (e.g. while a child `<Draggable>` is mid-drag, so Lynx's\n * native pan gesture doesn't steal the touch). Maps to Lynx's\n * `enable-scroll` attribute.\n */\n & Define.Prop<'enable-scroll', boolean, false>\n & Define.Prop<'class', string, false>\n & Define.Prop<'style', Record<string, string | number>, false>\n & Define.Slot<'default'>;\n\n/**\n * MT-thread `<scroll-view>` wrapper that mirrors scroll position into a\n * `SharedValue`. Pair with `useAnimatedStyle` for parallax / fade / scale\n * effects driven by scroll, all running on MT with zero per-frame thread\n * crossings.\n *\n * The component is the API; the inline `'main thread'` worklet, the\n * `__FlushElementTree()` trigger, and the runtime registration are all\n * internal. Users just pass a `SharedValue<number>` for the axis they care\n * about — same shape as `<Draggable translateX={tx}>`.\n *\n * @example Parallax header\n * ```tsx\n * const scrollY = useSharedValue(0);\n * const headerRef = useMainThreadRef<MainThread.Element | null>(null);\n *\n * useAnimatedStyle(headerRef, scrollY, 'translateY', {\n * inputRange: [0, 300], outputRange: [0, -150], extrapolate: 'clamp',\n * });\n *\n * <ScrollView offsetY={scrollY}>\n * <view main-thread:ref={headerRef}><image src={hero} /></view>\n * <text>Body…</text>\n * </ScrollView>\n * ```\n *\n * @example BG-reactive scroll readout\n * ```tsx\n * const scrollY = useSharedValue(0);\n * <ScrollView offsetY={scrollY}>...</ScrollView>\n * <text>Scrolled: {scrollY.value.toFixed(0)}px</text>\n * ```\n */\nexport const ScrollView = component<ScrollViewProps>(({ props, slots }) => {\n // Always allocate fallback SharedValues — hooks must run unconditionally.\n // The render closure picks between own/external; the worklet always sees\n // a defined SharedValue in its `_c` capture.\n const ownX = useSharedValue(0);\n const ownY = useSharedValue(0);\n\n // Phase 2.12 ScrollView ↔ child-gesture coordination. Descendant\n // `<Draggable>` / `<Swipeable>` flip this signal during their drag so the\n // UIKit `panGestureRecognizer` (which doesn't participate in the new\n // gesture arena) yields the touch. See `scroll-context.ts` for the why.\n const dragging = signal(false);\n\n // Phase 2.13: publish the scroll-view's element ref through the context so\n // descendants can drive scroll directly from worklets (e.g. <Draggable\n // edgeScroll>). Captured at setup so the worklet `_c` map sees a stable\n // ref identity.\n const scrollViewRef = useMainThreadRef<MainThread.Element | null>(null);\n\n // Pick the axis SVs once; the same identity is shared with descendants via\n // the context (so they can read live scroll position) and used at render\n // time for the bindscroll worklet's `_c` capture.\n const x: SharedValue<number> = props.offsetX ?? ownX;\n const y: SharedValue<number> = props.offsetY ?? ownY;\n const scrollOrientation = props['scroll-orientation'] ?? 'vertical';\n\n defineProvide(useScrollContext, () => ({\n dragging,\n scrollViewRef,\n offsetX: x,\n offsetY: y,\n scrollOrientation,\n }));\n\n return () => {\n // Compose user-passed enable-scroll with the descendant-driven flag:\n // both must be true. User can still force-lock by passing `false`.\n const userEnableScroll = props['enable-scroll'] ?? true;\n const enableScroll = userEnableScroll && !dragging.value;\n return (\n <scroll-view\n main-thread:ref={scrollViewRef}\n scroll-orientation={scrollOrientation}\n enable-scroll={enableScroll}\n class={props.class}\n style={props.style}\n main-thread-bindscroll={(e: any) => {\n 'main thread';\n y.current.value = e.detail.scrollTop;\n x.current.value = e.detail.scrollLeft;\n // Apply useAnimatedStyle bindings on the same frame. Inlined\n // (rather than calling a helper) because plain function imports\n // don't survive worklet `_c` capture across the MT bundle —\n // same constraint @sigx/lynx-motion's `animate()` documents.\n const __flush = (globalThis as Record<string, unknown>)['__FlushElementTree'] as (() => void) | undefined;\n if (__flush) __flush();\n }}\n >\n {slots.default?.()}\n </scroll-view>\n );\n };\n});\n"],"mappings":"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"}
|
package/dist/use-pinch.d.ts
CHANGED
package/dist/use-pinch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-pinch.d.ts","sourceRoot":"","sources":["../src/use-pinch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAsC,MAAM,
|
|
1
|
+
{"version":3,"file":"use-pinch.d.ts","sourceRoot":"","sources":["../src/use-pinch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAsC,MAAM,SAAS,CAAC;AAGnG;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAoGtE"}
|
package/dist/use-rotation.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-rotation.d.ts","sourceRoot":"","sources":["../src/use-rotation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAIlB,MAAM,
|
|
1
|
+
{"version":3,"file":"use-rotation.d.ts","sourceRoot":"","sources":["../src/use-rotation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAIlB,MAAM,SAAS,CAAC;AAGjB;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CA+G/E"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-gestures",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Gesture system for sigx-lynx - declarative composables for tap, pan, pinch, swipe, long press",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
10
13
|
},
|
|
11
14
|
"files": [
|
|
12
15
|
"src",
|
|
@@ -25,15 +28,15 @@
|
|
|
25
28
|
"author": "Andreas Ekdahl",
|
|
26
29
|
"license": "MIT",
|
|
27
30
|
"dependencies": {
|
|
28
|
-
"@sigx/lynx": "^0.
|
|
31
|
+
"@sigx/lynx": "^0.4.0"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@lynx-js/react": "^0.119.0",
|
|
32
35
|
"typescript": "^6.0.3",
|
|
33
36
|
"vite": "^8.0.12",
|
|
34
|
-
"@sigx/lynx-
|
|
35
|
-
"@sigx/lynx-
|
|
36
|
-
"@sigx/lynx-
|
|
37
|
+
"@sigx/lynx-runtime-main": "^0.4.0",
|
|
38
|
+
"@sigx/lynx-testing": "^0.4.0",
|
|
39
|
+
"@sigx/lynx-plugin": "^0.4.0"
|
|
37
40
|
},
|
|
38
41
|
"repository": {
|
|
39
42
|
"type": "git",
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type Define,
|
|
9
9
|
type MainThread,
|
|
10
10
|
} from '@sigx/lynx';
|
|
11
|
-
import { useScrollContext } from '../scroll-context
|
|
11
|
+
import { useScrollContext } from '../scroll-context';
|
|
12
12
|
|
|
13
13
|
export type ScrollViewProps =
|
|
14
14
|
& Define.Prop<'offsetX', SharedValue<number>, false>
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// changes. The other legacy hooks (`useTap`, `useLongPress`, `usePan`,
|
|
6
6
|
// `useFling`, `useSwipe`, `useGesture`, `usePanResponder`) were deleted in
|
|
7
7
|
// Phase 2.12.4 — use `Gesture.*` + `useGestureDetector` instead.
|
|
8
|
-
export { usePinch } from './use-pinch
|
|
9
|
-
export { useRotation } from './use-rotation
|
|
8
|
+
export { usePinch } from './use-pinch';
|
|
9
|
+
export { useRotation } from './use-rotation';
|
|
10
10
|
|
|
11
11
|
// Cross-thread value primitive — re-exported from @sigx/lynx for back-compat.
|
|
12
12
|
// @deprecated since 0.3.0 — import directly from '@sigx/lynx' instead.
|
|
@@ -31,20 +31,20 @@ export type {
|
|
|
31
31
|
} from '@sigx/lynx';
|
|
32
32
|
|
|
33
33
|
// Built-in MT components (arena-driven via `Gesture.*` + `useGestureDetector`).
|
|
34
|
-
export { Pressable } from './components/Pressable
|
|
35
|
-
export type { PressableProps } from './components/Pressable
|
|
36
|
-
export { Draggable } from './components/Draggable
|
|
37
|
-
export type { DraggableProps, DragEndDetail } from './components/Draggable
|
|
38
|
-
export { Swipeable } from './components/Swipeable
|
|
39
|
-
export type { SwipeableProps, SwipeSide } from './components/Swipeable
|
|
40
|
-
export { ScrollView } from './components/ScrollView
|
|
41
|
-
export type { ScrollViewProps } from './components/ScrollView
|
|
34
|
+
export { Pressable } from './components/Pressable';
|
|
35
|
+
export type { PressableProps } from './components/Pressable';
|
|
36
|
+
export { Draggable } from './components/Draggable';
|
|
37
|
+
export type { DraggableProps, DragEndDetail } from './components/Draggable';
|
|
38
|
+
export { Swipeable } from './components/Swipeable';
|
|
39
|
+
export type { SwipeableProps, SwipeSide } from './components/Swipeable';
|
|
40
|
+
export { ScrollView } from './components/ScrollView';
|
|
41
|
+
export type { ScrollViewProps } from './components/ScrollView';
|
|
42
42
|
|
|
43
43
|
// ScrollView ↔ child-gesture coordination (Phase 2.12.3). Public mostly so
|
|
44
44
|
// custom gesture components can opt in to the same auto-yield behavior that
|
|
45
45
|
// `<Draggable>` and `<Swipeable>` get for free.
|
|
46
|
-
export { useScrollContext } from './scroll-context
|
|
47
|
-
export type { ScrollContext } from './scroll-context
|
|
46
|
+
export { useScrollContext } from './scroll-context';
|
|
47
|
+
export type { ScrollContext } from './scroll-context';
|
|
48
48
|
|
|
49
49
|
// Types
|
|
50
50
|
export type {
|
|
@@ -58,4 +58,4 @@ export type {
|
|
|
58
58
|
RotationState,
|
|
59
59
|
UseRotationOptions,
|
|
60
60
|
UseRotationReturn,
|
|
61
|
-
} from './types
|
|
61
|
+
} from './types';
|
package/src/use-pinch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { signal } from '@sigx/lynx';
|
|
2
|
-
import type { UsePinchOptions, UsePinchReturn, TouchEvent, PinchState, TouchPoint } from './types
|
|
3
|
-
import { distance, midpoint } from './utils
|
|
2
|
+
import type { UsePinchOptions, UsePinchReturn, TouchEvent, PinchState, TouchPoint } from './types';
|
|
3
|
+
import { distance, midpoint } from './utils';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Two-finger pinch/zoom gesture.
|
package/src/use-rotation.ts
CHANGED
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
TouchEvent,
|
|
6
6
|
TouchPoint,
|
|
7
7
|
RotationState,
|
|
8
|
-
} from './types
|
|
9
|
-
import { angle, angleDelta, distance, midpoint } from './utils
|
|
8
|
+
} from './types';
|
|
9
|
+
import { angle, angleDelta, distance, midpoint } from './utils';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Two-finger rotation gesture.
|