@sigx/lynx-gestures 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +358 -0
- package/dist/components/Draggable.d.ts +66 -0
- package/dist/components/Draggable.d.ts.map +1 -0
- package/dist/components/Pressable.d.ts +40 -0
- package/dist/components/Pressable.d.ts.map +1 -0
- package/dist/components/ScrollView.d.ts +46 -0
- package/dist/components/ScrollView.d.ts.map +1 -0
- package/dist/components/Swipeable.d.ts +40 -0
- package/dist/components/Swipeable.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +401 -0
- package/dist/index.js.map +1 -0
- package/dist/scroll-context.d.ts +65 -0
- package/dist/scroll-context.d.ts.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/use-pinch.d.ts +14 -0
- package/dist/use-pinch.d.ts.map +1 -0
- package/dist/use-rotation.d.ts +13 -0
- package/dist/use-rotation.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +54 -0
- package/src/components/Draggable.tsx +450 -0
- package/src/components/Pressable.tsx +175 -0
- package/src/components/ScrollView.tsx +123 -0
- package/src/components/Swipeable.tsx +220 -0
- package/src/index.ts +61 -0
- package/src/scroll-context.ts +72 -0
- package/src/types.ts +87 -0
- package/src/use-pinch.ts +106 -0
- package/src/use-rotation.ts +129 -0
- package/src/utils.ts +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Andreas Ekdahl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# @sigx/lynx-gestures
|
|
2
|
+
|
|
3
|
+
Declarative, **frame-locked** gesture and animation primitives for [SignalX](https://github.com/signalxjs) on Lynx. Touch handlers, drag/swipe components, and animation linkage all run on the platform's main UI thread — your gestures track the finger at the display refresh rate even when the JS thread is busy fetching, parsing, or re-rendering.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Built-in gesture components** — `<Pressable>`, `<Draggable>`, `<Swipeable>` — drop in for instant 60/120 fps interactions, no worklet plumbing in user code.
|
|
8
|
+
- **Main-Thread Scripting under the hood** — touch handlers, transform updates, and visual feedback run on Lynx's main thread (Lepus) so gestures don't block on your background JS.
|
|
9
|
+
- **Background-thread composables** — `useTap`, `useLongPress`, `usePan`, `usePinch`, `useSwipe`, `useRotation`, `useFling`, `usePanResponder`, and a `useGesture` composer with simultaneous / exclusive / sequential relations.
|
|
10
|
+
- **Composition utilities** — `mergeHandlers`, gesture composers, render-prop slots for swipe-to-reveal actions.
|
|
11
|
+
|
|
12
|
+
> **Cross-thread primitive moved + renamed.** `useSharedValue`, `SharedValue` (formerly `useAnimatedValue` / `AnimatedValue`), and `useAnimatedStyle` were promoted to [`@sigx/lynx`](../lynx) in 0.3.0 — they have no gesture coupling and now live next to `MainThreadRef` (their base class). The primitive was renamed in Phase 2.8 to reflect that it's a general MT/BG bridge — animation is one customer; gestures and scroll are equally first-class. The old `useAnimatedValue` / `AnimatedValue` import paths still work via deprecated re-exports for one minor cycle; please import `useSharedValue` / `SharedValue` from `@sigx/lynx` directly in new code.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @sigx/lynx-gestures
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> Requires `@sigx/lynx` as a peer dependency. The build pipeline (`@sigx/lynx-plugin`) handles the `'main thread'` worklet transform automatically.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { signal, component, useSharedValue } from '@sigx/lynx';
|
|
26
|
+
import { Pressable, Draggable, Swipeable } from '@sigx/lynx-gestures';
|
|
27
|
+
|
|
28
|
+
const App = component(() => {
|
|
29
|
+
const taps = signal(0);
|
|
30
|
+
const dragX = useSharedValue(0);
|
|
31
|
+
|
|
32
|
+
return () => (
|
|
33
|
+
<view>
|
|
34
|
+
{/* Tap with instant visual feedback */}
|
|
35
|
+
<Pressable
|
|
36
|
+
pressedOpacity={0.5}
|
|
37
|
+
pressedScale={0.95}
|
|
38
|
+
onPress={() => { taps.value++; }}
|
|
39
|
+
style={{ width: '100px', height: '100px', backgroundColor: '#3b82f6' }}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
{/* Drag at native frame rate; observe position on BG */}
|
|
43
|
+
<Draggable
|
|
44
|
+
translateX={dragX}
|
|
45
|
+
snapBack
|
|
46
|
+
onDragEnd={(e) => console.log('released at', e.x, e.y)}
|
|
47
|
+
style={{ width: '90px', height: '90px', backgroundColor: '#a855f7' }}
|
|
48
|
+
/>
|
|
49
|
+
<text>BG sees x = {dragX.value}</text>
|
|
50
|
+
|
|
51
|
+
{/* Swipe-to-reveal */}
|
|
52
|
+
<Swipeable
|
|
53
|
+
rightActions={() => <view><text>Delete</text></view>}
|
|
54
|
+
onSwipeOpen={(e) => console.log('opened', e.side)}
|
|
55
|
+
>
|
|
56
|
+
<view><text>Swipe me</text></view>
|
|
57
|
+
</Swipeable>
|
|
58
|
+
</view>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Why this exists — the architecture
|
|
66
|
+
|
|
67
|
+
### The two-thread model
|
|
68
|
+
|
|
69
|
+
Lynx runs your app on two JS contexts:
|
|
70
|
+
|
|
71
|
+
- **Background (BG) thread** — your component code, signals, effects, fetch/parse, JSX renders.
|
|
72
|
+
- **Main (MT) thread** — the renderer's commit thread, where native draw calls happen.
|
|
73
|
+
|
|
74
|
+
A naive touch handler runs on BG, mutates a signal, triggers a re-render, the renderer diffs styles, queues an op, the op crosses to MT, MT commits. **Two thread crossings per touchmove**, plus a JSX render and a JSON marshal. At 120 Hz touch input, that pipeline can't keep up — the cursor visibly lags the finger and chatters under GC pressure.
|
|
75
|
+
|
|
76
|
+
### How `@sigx/lynx-gestures` solves it
|
|
77
|
+
|
|
78
|
+
Gesture components mark their touch handlers as `'main thread'` worklets. The build pipeline extracts those handlers, ships them to MT once at startup, and Lynx native dispatches touch events directly to them. The handler then mutates a `SharedValue` (a thread-aware ref) and calls `setStyleProperties` on the bound element — all on the MT thread, **zero crossings**, no JSX render, no JSON.
|
|
79
|
+
|
|
80
|
+
### Cross-thread observability
|
|
81
|
+
|
|
82
|
+
When you pass a `SharedValue` to a gesture component, the MT thread continuously writes to it. A bridge publishes those writes to the BG thread once per native flush (typically per frame), where they land in a `signal`-style mirror. Your `effect(() => sv.value)` re-runs reactively without injecting BG into the gesture hot path.
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
MT thread: tx.current.value = 50 ─┐
|
|
86
|
+
MT thread: setStyleProperties(...) │ one event per flush
|
|
87
|
+
↓ __FlushElementTree │ with [wvid, value] tuples
|
|
88
|
+
↓ flushAvBridgePublishes ────────┘
|
|
89
|
+
BG thread: signal value = 50 → effect re-runs, debounce, fetch, etc.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Built-in components
|
|
95
|
+
|
|
96
|
+
### `<Pressable>`
|
|
97
|
+
|
|
98
|
+
Tap and long-press with optional visual feedback. The opacity and scale flash apply on MT inside the touchstart worklet, so feedback is visually instantaneous.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Pressable
|
|
102
|
+
pressedOpacity={0.5}
|
|
103
|
+
pressedScale={0.95}
|
|
104
|
+
longPressDuration={500}
|
|
105
|
+
onPress={() => doThing()}
|
|
106
|
+
onLongPress={() => doOtherThing()}
|
|
107
|
+
style={{ ... }}
|
|
108
|
+
>
|
|
109
|
+
<text>Press me</text>
|
|
110
|
+
</Pressable>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Prop | Type | Default | Description |
|
|
114
|
+
| -------------------- | --------- | ------- | -------------------------------------------------------- |
|
|
115
|
+
| `pressedOpacity` | `number` | — | Opacity to apply on press, restored on release. |
|
|
116
|
+
| `pressedScale` | `number` | — | `scale()` factor on press, restored on release. |
|
|
117
|
+
| `longPressDuration` | `number` | `500` | ms to hold before `onLongPress` fires. |
|
|
118
|
+
| `maxDistance` | `number` | `10` | Move threshold (px) above which press is cancelled. |
|
|
119
|
+
| `disabled` | `boolean` | `false` | Suppresses both events and visual feedback. |
|
|
120
|
+
| `onPress` | event | — | Fires on tap (touchend within `maxDistance`). |
|
|
121
|
+
| `onLongPress` | event | — | Fires after `longPressDuration` if still pressed. |
|
|
122
|
+
|
|
123
|
+
### `<Draggable>`
|
|
124
|
+
|
|
125
|
+
Pan-to-translate on the MT thread, with optional axis lock, bounds clamping, snap-back, and `SharedValue` exposure of the position.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
const tx = useSharedValue(0);
|
|
129
|
+
const ty = useSharedValue(0);
|
|
130
|
+
|
|
131
|
+
<Draggable
|
|
132
|
+
axis="both"
|
|
133
|
+
threshold={4}
|
|
134
|
+
snapBack
|
|
135
|
+
minX={-100} maxX={100}
|
|
136
|
+
translateX={tx} translateY={ty}
|
|
137
|
+
onDragStart={(e) => console.log('start', e.x, e.y)}
|
|
138
|
+
onDragEnd={(e) => console.log('end', e.x, e.y, 'velocity', e.vx, e.vy)}
|
|
139
|
+
>
|
|
140
|
+
<view style={{ width: '90px', height: '90px', backgroundColor: '#a855f7' }} />
|
|
141
|
+
</Draggable>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Prop | Type | Default | Description |
|
|
145
|
+
| ------------------- | ------------------------------- | ------- | -------------------------------------------------------- |
|
|
146
|
+
| `axis` | `'x' \| 'y' \| 'both'` | `'both'`| Restrict motion to one axis. |
|
|
147
|
+
| `threshold` | `number` | `0` | Min distance (px) before recognition fires. |
|
|
148
|
+
| `snapBack` | `boolean` | `false` | Animate back to origin on release. |
|
|
149
|
+
| `minX`/`maxX`/`minY`/`maxY` | `number` | — | Clamp the translation range. |
|
|
150
|
+
| `translateX` | `SharedValue<number>` | — | External SharedValue the worklet writes on every touchmove. |
|
|
151
|
+
| `translateY` | `SharedValue<number>` | — | Same, for the Y axis. |
|
|
152
|
+
| `onDragStart` | event `{ x, y }` | — | Fires once per gesture after threshold is met. |
|
|
153
|
+
| `onDragEnd` | event `{ x, y, vx, vy }` | — | Fires on release; includes terminal velocity. |
|
|
154
|
+
|
|
155
|
+
### `<Swipeable>`
|
|
156
|
+
|
|
157
|
+
Horizontal swipe-to-reveal with up to two action panels. Uses `MTElementWrapper.animate()` for the snap, so the easing curve runs on the native compositor.
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<Swipeable
|
|
161
|
+
leftActions={() => <view style={{ backgroundColor: '#22c55e' }}><text>Archive</text></view>}
|
|
162
|
+
rightActions={() => <view style={{ backgroundColor: '#ef4444' }}><text>Delete</text></view>}
|
|
163
|
+
onSwipeOpen={(e) => console.log('opened', e.side)}
|
|
164
|
+
onSwipeClose={() => console.log('closed')}
|
|
165
|
+
>
|
|
166
|
+
<view><text>Row content</text></view>
|
|
167
|
+
</Swipeable>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Prop | Type | Default | Description |
|
|
171
|
+
| --------------------- | -------------------------------- | ------- | -------------------------------------------------------- |
|
|
172
|
+
| `leftActionsWidth` | `number` | `100` | Width (px) of the left reveal panel. |
|
|
173
|
+
| `rightActionsWidth` | `number` | `100` | Width (px) of the right reveal panel. |
|
|
174
|
+
| `snapThreshold` | `number` | `40` | Min translation before snapping to the open position. |
|
|
175
|
+
| `snapDuration` | `number` | `200` | Snap animation duration (ms). |
|
|
176
|
+
| `leftActions` | `() => JSX` | — | Render-prop for the left panel. |
|
|
177
|
+
| `rightActions` | `() => JSX` | — | Render-prop for the right panel. |
|
|
178
|
+
| `onSwipeOpen` | event `{ side: 'left' \| 'right' }` | — | Fires when the row snaps open. |
|
|
179
|
+
| `onSwipeClose` | event | — | Fires when the row snaps closed from an open position. |
|
|
180
|
+
|
|
181
|
+
### `<ScrollView>`
|
|
182
|
+
|
|
183
|
+
MT-thread `<scroll-view>` wrapper that mirrors scroll position into a `SharedValue`. Pair with `useAnimatedStyle` for parallax / fade / scale effects driven by scroll — all running on MT with zero per-frame thread crossings.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { useSharedValue, useAnimatedStyle, useMainThreadRef } from '@sigx/lynx';
|
|
187
|
+
import { ScrollView } from '@sigx/lynx-gestures';
|
|
188
|
+
|
|
189
|
+
const scrollY = useSharedValue(0);
|
|
190
|
+
const headerRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
191
|
+
|
|
192
|
+
useAnimatedStyle(headerRef, scrollY, 'translateY', {
|
|
193
|
+
inputRange: [0, 300], outputRange: [0, -150], extrapolate: 'clamp',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
<ScrollView offsetY={scrollY}>
|
|
197
|
+
<view main-thread:ref={headerRef}><image src={hero} /></view>
|
|
198
|
+
<text>Body…</text>
|
|
199
|
+
<text>Scroll: {scrollY.value.toFixed(0)}px</text>
|
|
200
|
+
</ScrollView>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
| Prop | Type | Default | Description |
|
|
204
|
+
| --------------------- | -------------------------------- | ------------ | -------------------------------------------------------- |
|
|
205
|
+
| `offsetY` | `SharedValue<number>` | — | External SharedValue the worklet writes on every scroll. |
|
|
206
|
+
| `offsetX` | `SharedValue<number>` | — | Same, for the horizontal axis. |
|
|
207
|
+
| `scroll-orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Pass-through to `<scroll-view>`. |
|
|
208
|
+
| `class` / `style` | string / object | — | Pass-through styling. |
|
|
209
|
+
|
|
210
|
+
The component handles the inline `'main thread'` worklet, the SharedValue writes, and the `__FlushElementTree()` trigger internally. Users only see `SharedValue`s.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Animation primitives
|
|
215
|
+
|
|
216
|
+
> The cross-thread primitive — `useSharedValue`, `SharedValue`, `useAnimatedStyle` — lives in [`@sigx/lynx`](../lynx#sharedvalue--the-cross-thread-primitive) since 0.3.0. Import from `@sigx/lynx` directly:
|
|
217
|
+
|
|
218
|
+
### `useSharedValue<T>(initial)` *(from `@sigx/lynx`)*
|
|
219
|
+
|
|
220
|
+
Allocates a thread-aware value: writeable on MT, reactively observable on BG.
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { useSharedValue } from '@sigx/lynx';
|
|
224
|
+
|
|
225
|
+
const tx = useSharedValue(0);
|
|
226
|
+
|
|
227
|
+
// MT (inside a 'main thread' worklet)
|
|
228
|
+
tx.current.value = 50;
|
|
229
|
+
|
|
230
|
+
// BG (in component body, effect, computed, JSX)
|
|
231
|
+
console.log(tx.value);
|
|
232
|
+
effect(() => console.log('tx is now', tx.value));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`sv.current.value` is the MT-side read/write path (the underlying `MainThreadRef` envelope). `sv.value` is the BG-side reactive read. Writes on BG are read-only (a dev warning fires); the canonical mutation path is the MT worklet.
|
|
236
|
+
|
|
237
|
+
The bridge coalesces writes per native flush — N MT mutations within one frame land as one BG event with N tuples.
|
|
238
|
+
|
|
239
|
+
### `useAnimatedStyle(elRef, sv, mapperName, params?)` *(from `@sigx/lynx`)*
|
|
240
|
+
|
|
241
|
+
Bind an element's style to a `SharedValue` via a named mapper. The mapper runs on MT every flush where the SharedValue's value changed.
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { useMainThreadRef, useSharedValue, useAnimatedStyle } from '@sigx/lynx';
|
|
245
|
+
|
|
246
|
+
const tx = useSharedValue(0);
|
|
247
|
+
const ghostRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
248
|
+
|
|
249
|
+
useAnimatedStyle(ghostRef, tx, 'translateX', { factor: 0.5 });
|
|
250
|
+
useAnimatedStyle(ghostRef, tx, 'opacity', { factor: -0.01, offset: 1 });
|
|
251
|
+
|
|
252
|
+
<Draggable translateX={tx} />
|
|
253
|
+
<view main-thread:ref={ghostRef} style={{ ... }} />
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The ghost view tracks the draggable at half speed and fades as it moves — without a single thread crossing per frame.
|
|
257
|
+
|
|
258
|
+
### Built-in mappers
|
|
259
|
+
|
|
260
|
+
`translateX`, `translateY`, `scale`, and `opacity` accept **either** a linear `{ factor, offset }` shape **or** a range-mapping `{ inputRange, outputRange, extrapolate? }` shape (see "Range mapping" below).
|
|
261
|
+
|
|
262
|
+
| Name | Linear param shape | Output |
|
|
263
|
+
| ------------ | ------------------------------------------ | --------------------------------------------------- |
|
|
264
|
+
| `translateX` | `{ factor?: number }` | `transform: translateX(value * factor)px` |
|
|
265
|
+
| `translateY` | `{ factor?: number }` | `transform: translateY(value * factor)px` |
|
|
266
|
+
| `translate` | `{ factorX?: number; factorY?: number }` | `transform: translate(v.x*fx, v.y*fy)px` (2D SharedValue) |
|
|
267
|
+
| `scale` | `{ offset?: number }` | `transform: scale(value + offset)` |
|
|
268
|
+
| `opacity` | `{ factor?: number; offset?: number }` | `opacity` clamped to `[0, 1]` of `value*f + o` |
|
|
269
|
+
| `rotate` | (none) | `transform: rotate(value)deg` |
|
|
270
|
+
|
|
271
|
+
When multiple bindings on the same element produce a `transform`, the parts concatenate in registration order. Other style keys merge; later registrations win on duplicate keys. Whenever **any** binding on an element ticks, **all** of its bindings re-run so partial outputs don't drop the unchanged-axis contribution.
|
|
272
|
+
|
|
273
|
+
### Range mapping
|
|
274
|
+
|
|
275
|
+
`translateX` / `translateY` / `scale` / `opacity` also accept `{ inputRange, outputRange, extrapolate? }` — handy for scroll-driven UIs:
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
const { y, onScroll } = useScrollViewOffset();
|
|
279
|
+
const headerRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
280
|
+
|
|
281
|
+
// Parallax: scroll 0..300 → translateY 0..-150, clamped beyond.
|
|
282
|
+
useAnimatedStyle(headerRef, y, 'translateY', {
|
|
283
|
+
inputRange: [0, 300], outputRange: [0, -150], extrapolate: 'clamp',
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Multi-stop ranges (length ≥ 2) work — each segment is interpolated independently. `extrapolate: 'clamp'` (default) caps at the endpoints; `'identity'` extends linearly using the slope of the nearest segment.
|
|
288
|
+
|
|
289
|
+
### Custom mappers
|
|
290
|
+
|
|
291
|
+
You can register additional mappers from MT-side code:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
// in a 'main thread'-marked module
|
|
295
|
+
import { registerMapper } from '@sigx/lynx-runtime-main';
|
|
296
|
+
|
|
297
|
+
registerMapper('skewX', (v) => ({ transform: `skewX(${v}deg)` }));
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Then use the name from BG: `useAnimatedStyle(elRef, sv, 'skewX')`. The string name is what crosses the build pipeline; the function lives on MT.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Background-thread composables
|
|
305
|
+
|
|
306
|
+
For cases where you don't need MT-thread tracking (state machines that drive non-visual logic, gestures over scroll lists, or coordinating multiple recognizers at once), the package also ships background-thread recognizers exposing `signal`-based state.
|
|
307
|
+
|
|
308
|
+
| Composable | Returns |
|
|
309
|
+
| ----------------- | ------------------------------------------------ |
|
|
310
|
+
| `useTap` | tap state + handlers; `onTap`, `onDoubleTap` |
|
|
311
|
+
| `useLongPress` | long-press detection |
|
|
312
|
+
| `usePan` | drag distance / velocity |
|
|
313
|
+
| `usePinch` | scale, focal point |
|
|
314
|
+
| `useSwipe` | direction + distance |
|
|
315
|
+
| `useRotation` | two-finger rotation in radians |
|
|
316
|
+
| `useFling` | velocity-gated flick |
|
|
317
|
+
| `usePanResponder` | RN-shape `onStartShouldSet` / `onMove` / etc. |
|
|
318
|
+
| `useGesture` | composer (simultaneous / exclusive / sequential) |
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
const pan = usePan({
|
|
322
|
+
onMove: (state) => console.log(state.dx, state.dy),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
<view {...pan.handlers} />
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
These are simpler to compose and fully introspectable on BG, at the cost of a thread crossing per gesture event. For visual feedback (translate, scale, opacity), prefer the MT components above.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Performance notes
|
|
333
|
+
|
|
334
|
+
- **Avoid changing the gesture component's `style` prop on every render.** A BG-side `SET_STYLE` op for the same element being dragged can clobber MT-side `setStyleProperties` writes. The framework guards this with shallow-equal in the style patcher, so structurally-stable inline styles (`style={{ width: '90px', ... }}`) are fine. Computed-per-render styles touching the dragged element are the case to watch.
|
|
335
|
+
- **Pass MT-locals through `runOnBackground` arguments**, not through closure capture. The BG-bound function only sees what crossed the bridge — its parameter list. Capturing a `let side = …` declared inside the worklet body will fail at runtime with `ReferenceError` because BG never had `side`.
|
|
336
|
+
- **Per-SharedValue `===` diff coalescing** means object-typed SharedValues (`useSharedValue<{x,y}>`) only publish on identity change, not on property mutation. Use scalar SharedValues and compose them, or use the `translate` mapper which takes a 2D value.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Testing
|
|
341
|
+
|
|
342
|
+
The package ships with two test layers:
|
|
343
|
+
|
|
344
|
+
- **Source-shape regex tests** verify that `'main thread'` directives, handler attribute spellings, and worklet captures are all in place. Fast; run as part of `pnpm test`.
|
|
345
|
+
- **MT end-to-end tests** (`pnpm --filter @sigx/lynx-gestures test:mt`) actually run the SWC LEPUS transform on the live source, eval the resulting `registerWorkletInternal` calls into the upstream worklet runtime under vitest, and drive synthetic touches through the registered worklets — catching the class of bug where a refactor breaks the worklet pipeline silently.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Related
|
|
350
|
+
|
|
351
|
+
- [`@sigx/lynx`](../lynx) — the framework barrel; import everything from here.
|
|
352
|
+
- [`@sigx/runtime-lynx`](../runtime-lynx) — background-thread renderer and signal/effect wiring.
|
|
353
|
+
- [`@sigx/lynx-runtime-main`](../runtime-lynx-main) — main-thread runtime and PAPI integration.
|
|
354
|
+
- [`@sigx/lynx-plugin`](../lynx-plugin) — the rspack/rspeedy plugin that runs the worklet transform at build time.
|
|
355
|
+
|
|
356
|
+
## License
|
|
357
|
+
|
|
358
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type SharedValue, type Define } from '@sigx/lynx';
|
|
2
|
+
export interface DragEndDetail {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
vx: number;
|
|
6
|
+
vy: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Edge-scroll configuration for `<Draggable edgeScroll>`. Either `true` for
|
|
10
|
+
* default tuning, or an object overriding the defaults.
|
|
11
|
+
*/
|
|
12
|
+
export type EdgeScrollConfig = boolean | {
|
|
13
|
+
/** Distance from viewport edge in pt where auto-scroll engages. Default 50. */
|
|
14
|
+
threshold?: number;
|
|
15
|
+
/** Maximum scroll velocity in pt/sec at the edge. Default 800. */
|
|
16
|
+
maxSpeed?: number;
|
|
17
|
+
};
|
|
18
|
+
export type DraggableProps = Define.Prop<'axis', 'x' | 'y' | 'both', false> & Define.Prop<'threshold', number, false> & Define.Prop<'snapBack', boolean, false> & Define.Prop<'minX', number, false> & Define.Prop<'maxX', number, false> & Define.Prop<'minY', number, false> & Define.Prop<'maxY', number, false> & Define.Prop<'translateX', SharedValue<number>, false> & Define.Prop<'translateY', SharedValue<number>, false> & Define.Prop<'edgeScroll', EdgeScrollConfig, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Slot<'default'> & Define.Event<'dragStart', {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}> & Define.Event<'dragEnd', DragEndDetail>;
|
|
22
|
+
/**
|
|
23
|
+
* MT-thread draggable container, built on the native gesture arena via
|
|
24
|
+
* `Gesture.Pan()`. The bound element's transform is driven by two
|
|
25
|
+
* `useAnimatedStyle` bindings (one per axis) — the same primitive any user
|
|
26
|
+
* could compose. The Pan onUpdate worklet writes to the SharedValues; the
|
|
27
|
+
* bridge applies the transform on the next flush boundary, composing the
|
|
28
|
+
* two bindings into a single `setStyleProperties({ transform })` call.
|
|
29
|
+
*
|
|
30
|
+
* Because the visible position is bridge-driven rather than written directly
|
|
31
|
+
* by the worklet, external animation of `translateX`/`translateY` (e.g.
|
|
32
|
+
* `withSpring(tx, 0)` to spring back to origin after release) moves the
|
|
33
|
+
* element visually for free — the binding picks up whichever SV write
|
|
34
|
+
* happened most recently, regardless of who wrote it.
|
|
35
|
+
*
|
|
36
|
+
* `dragStart` and `dragEnd` are dispatched to BG via `runOnBackground` (low
|
|
37
|
+
* frequency, cross-thread is fine).
|
|
38
|
+
*
|
|
39
|
+
* Unlike the prior `bindtouch*`-based implementation, the native pan gesture
|
|
40
|
+
* arena handles multi-touch correctly (secondary fingers don't cancel the
|
|
41
|
+
* primary drag).
|
|
42
|
+
*
|
|
43
|
+
* **Scroll composition** (Phase 2.12.3): Lynx's `<scroll-view>` doesn't
|
|
44
|
+
* participate in the new gesture arena, so without coordination both pan
|
|
45
|
+
* and scroll would fire concurrently. `<Draggable>` reads `useScrollContext`
|
|
46
|
+
* at setup; if a parent `<ScrollView>` is in scope, the BG-side dragStart/
|
|
47
|
+
* dragEnd flips `scrollCtx.dragging` automatically — the parent's
|
|
48
|
+
* `enable-scroll` is gated on that signal, so the UIKit pan recognizer
|
|
49
|
+
* yields for the duration of the drag. No consumer wiring required.
|
|
50
|
+
*
|
|
51
|
+
* **Edge-scroll** (Phase 2.13): pass `edgeScroll` to auto-scroll the parent
|
|
52
|
+
* `<ScrollView>` when the finger nears its viewport edge during a drag —
|
|
53
|
+
* the standard drag-to-reorder pattern (Apple Mail, iOS Reminders). The
|
|
54
|
+
* scroll axis follows `scrollOrientation` as published through the context.
|
|
55
|
+
* Inside the threshold zone the scroll velocity ramps from 0 at the
|
|
56
|
+
* threshold boundary to `maxSpeed` at the edge. Quietly no-ops if
|
|
57
|
+
* `edgeScroll` is unset OR the Draggable isn't nested in a ScrollView.
|
|
58
|
+
*
|
|
59
|
+
* Note on the native event payload: Lynx's pan handler emits `pageX`/`pageY`
|
|
60
|
+
* but no `translationX`/`velocityX` — we compute deltas and velocity from
|
|
61
|
+
* pageX/pageY ourselves (same as the prior touch-based implementation).
|
|
62
|
+
*/
|
|
63
|
+
export declare const Draggable: import("@sigx/runtime-core").ComponentFactory<DraggableProps, void, {
|
|
64
|
+
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
65
|
+
}>;
|
|
66
|
+
//# sourceMappingURL=Draggable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Draggable.d.ts","sourceRoot":"","sources":["../../src/components/Draggable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EAChB,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG;IACvC,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,EAAE,KAAK,CAAC,GAC9C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAClC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAClC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAClC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,GAClC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,GACrD,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,GACrD,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,gBAAgB,EAAE,KAAK,CAAC,GAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAkC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,SAAS;;EAuUpB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Define } from '@sigx/lynx';
|
|
2
|
+
export type PressableProps = Define.Prop<'pressedOpacity', number, false> & Define.Prop<'pressedScale', number, false> & Define.Prop<'longPressDuration', number, false> & Define.Prop<'maxDistance', number, false> & Define.Prop<'disabled', boolean, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Slot<'default'> & Define.Event<'press', void> & Define.Event<'longPress', void>;
|
|
3
|
+
/**
|
|
4
|
+
* MT-thread tap + long-press recognizer with built-in pressed-state visual
|
|
5
|
+
* feedback (opacity + scale). Press and long-press callbacks are dispatched
|
|
6
|
+
* to BG via `runOnBackground` (low-frequency cross-thread is fine).
|
|
7
|
+
*
|
|
8
|
+
* Cross-platform gesture-arena quirks (Phase 2.12.1, observed on iOS Lynx
|
|
9
|
+
* 3.5 sim and Android Lynx 3.6 / Pixel 9 Pro XL) make this component a
|
|
10
|
+
* hybrid: it composes `Gesture.Tap()` + `Gesture.LongPress()` via
|
|
11
|
+
* `Simultaneous` AND adds an onEnd-fallback path inside LongPress, so press
|
|
12
|
+
* emission works on both platforms via different routes:
|
|
13
|
+
*
|
|
14
|
+
* - **Android**: `Tap.onStart` fires on touch-up (as documented). Press
|
|
15
|
+
* emits there; the LongPress fallback sees `pressEmitted=true` and
|
|
16
|
+
* skips. `Tap.onEnd` fires on the same touch-up — but iOS's premature
|
|
17
|
+
* onEnd (next bullet) means we can't safely reset styles here, so style
|
|
18
|
+
* reset lives in LongPress.onEnd.
|
|
19
|
+
* - **iOS**: `Tap.onEnd` fires ~6ms after touchstart (an arena
|
|
20
|
+
* fail/reset path that doesn't trigger on Android). `Tap.onStart`
|
|
21
|
+
* never fires for our composition. We rely on `LongPress.onEnd` to
|
|
22
|
+
* detect "lift before duration with no movement" and emit press from
|
|
23
|
+
* the fallback. `Gesture.Race` would be simpler in theory, but its
|
|
24
|
+
* `waitFor` deadlocks Tap on iOS — the arena dispatches Tap before
|
|
25
|
+
* LongPress reaches Fail state.
|
|
26
|
+
*
|
|
27
|
+
* State tracks `longPressFired` and `pressEmitted` so neither event
|
|
28
|
+
* double-fires regardless of which platform path resolves first.
|
|
29
|
+
* Movement past `maxDistance` is tracked from `e.params.pageX/pageY`;
|
|
30
|
+
* `LongPress.onEnd` skips press emission when the touch drifted past
|
|
31
|
+
* the threshold (matching Tap's success criteria).
|
|
32
|
+
*
|
|
33
|
+
* Disabled is captured at setup; runtime toggling won't update an active
|
|
34
|
+
* gesture's behavior. Wrap the parent in conditional rendering for now if
|
|
35
|
+
* dynamic disable is needed.
|
|
36
|
+
*/
|
|
37
|
+
export declare const Pressable: import("@sigx/runtime-core").ComponentFactory<PressableProps, void, {
|
|
38
|
+
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=Pressable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pressable.d.ts","sourceRoot":"","sources":["../../src/components/Pressable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,cAAc,GACtB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC5C,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1C,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC/C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,GACzC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,GACvC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,GAC3B,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AASpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,SAAS;;EA+GpB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type SharedValue, type Define } from '@sigx/lynx';
|
|
2
|
+
export type ScrollViewProps = Define.Prop<'offsetX', SharedValue<number>, false> & Define.Prop<'offsetY', SharedValue<number>, false> & Define.Prop<'scroll-orientation', 'vertical' | 'horizontal', false>
|
|
3
|
+
/**
|
|
4
|
+
* Toggle native scroll responsiveness at runtime — set false to lock the
|
|
5
|
+
* scroll-view (e.g. while a child `<Draggable>` is mid-drag, so Lynx's
|
|
6
|
+
* native pan gesture doesn't steal the touch). Maps to Lynx's
|
|
7
|
+
* `enable-scroll` attribute.
|
|
8
|
+
*/
|
|
9
|
+
& Define.Prop<'enable-scroll', boolean, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Slot<'default'>;
|
|
10
|
+
/**
|
|
11
|
+
* MT-thread `<scroll-view>` wrapper that mirrors scroll position into a
|
|
12
|
+
* `SharedValue`. Pair with `useAnimatedStyle` for parallax / fade / scale
|
|
13
|
+
* effects driven by scroll, all running on MT with zero per-frame thread
|
|
14
|
+
* crossings.
|
|
15
|
+
*
|
|
16
|
+
* The component is the API; the inline `'main thread'` worklet, the
|
|
17
|
+
* `__FlushElementTree()` trigger, and the runtime registration are all
|
|
18
|
+
* internal. Users just pass a `SharedValue<number>` for the axis they care
|
|
19
|
+
* about — same shape as `<Draggable translateX={tx}>`.
|
|
20
|
+
*
|
|
21
|
+
* @example Parallax header
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const scrollY = useSharedValue(0);
|
|
24
|
+
* const headerRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
25
|
+
*
|
|
26
|
+
* useAnimatedStyle(headerRef, scrollY, 'translateY', {
|
|
27
|
+
* inputRange: [0, 300], outputRange: [0, -150], extrapolate: 'clamp',
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* <ScrollView offsetY={scrollY}>
|
|
31
|
+
* <view main-thread:ref={headerRef}><image src={hero} /></view>
|
|
32
|
+
* <text>Body…</text>
|
|
33
|
+
* </ScrollView>
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example BG-reactive scroll readout
|
|
37
|
+
* ```tsx
|
|
38
|
+
* const scrollY = useSharedValue(0);
|
|
39
|
+
* <ScrollView offsetY={scrollY}>...</ScrollView>
|
|
40
|
+
* <text>Scrolled: {scrollY.value.toFixed(0)}px</text>
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const ScrollView: import("@sigx/runtime-core").ComponentFactory<ScrollViewProps, void, {
|
|
44
|
+
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
45
|
+
}>;
|
|
46
|
+
//# sourceMappingURL=ScrollView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollView.d.ts","sourceRoot":"","sources":["../../src/components/ScrollView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,eAAe,GACvB,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,GAClD,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,GAClD,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,YAAY,EAAE,KAAK,CAAC;AACrE;;;;;GAKG;GACD,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,GAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,UAAU;;EA8DrB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Define } from '@sigx/lynx';
|
|
2
|
+
export type SwipeSide = 'left' | 'right';
|
|
3
|
+
export type SwipeableProps = Define.Prop<'leftActionsWidth', number, false> & Define.Prop<'rightActionsWidth', number, false> & Define.Prop<'snapThreshold', number, false> & Define.Prop<'snapDuration', number, false> & Define.Prop<'leftActions', () => unknown, false> & Define.Prop<'rightActions', () => unknown, false> & Define.Prop<'class', string, false> & Define.Prop<'style', Record<string, string | number>, false> & Define.Prop<'foregroundStyle', Record<string, string | number>, false> & Define.Slot<'default'> & Define.Event<'swipeOpen', {
|
|
4
|
+
side: SwipeSide;
|
|
5
|
+
}> & Define.Event<'swipeClose', void>;
|
|
6
|
+
/**
|
|
7
|
+
* Horizontal swipe-to-reveal container, built on the native gesture arena
|
|
8
|
+
* via `Gesture.Pan().axis('x')`. The foreground is dragged horizontally on
|
|
9
|
+
* the MT thread; on release it snaps to one of three resting positions
|
|
10
|
+
* (closed / open-left / open-right) using `MTElementWrapper.animate()`.
|
|
11
|
+
* Open and close events are dispatched to BG via `runOnBackground`.
|
|
12
|
+
*
|
|
13
|
+
* Migrated from a 4-`bindtouch*`-worklet implementation to a single
|
|
14
|
+
* `Gesture.Pan()` (Phase 2.12). Carries the same Phase 2.11 quirks:
|
|
15
|
+
* - `.onBegin(() => {})` no-op is load-bearing on iOS Pan to gate
|
|
16
|
+
* `_isInvokedBegin` open so onStart/onEnd fire.
|
|
17
|
+
* - `e.params.pageX` (not `e.pageX`) — Lynx pan event nests the touch
|
|
18
|
+
* payload under `params`.
|
|
19
|
+
*
|
|
20
|
+
* Supply `leftActions` and/or `rightActions` as render-prop functions:
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <Swipeable
|
|
24
|
+
* rightActions={() => <view><text>Delete</text></view>}
|
|
25
|
+
* onSwipeOpen={(e) => console.log('opened', e.side)}
|
|
26
|
+
* >
|
|
27
|
+
* <view><text>Row content</text></view>
|
|
28
|
+
* </Swipeable>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* **Scroll composition** (Phase 2.12.3): nesting `<Swipeable>` inside
|
|
32
|
+
* `<ScrollView>` is automatic — `useScrollContext` is read at setup and
|
|
33
|
+
* the BG-side onStart/onEnd handlers flip `scrollCtx.dragging` so the
|
|
34
|
+
* parent yields its UIKit pan for the duration of the swipe. No consumer
|
|
35
|
+
* wiring required.
|
|
36
|
+
*/
|
|
37
|
+
export declare const Swipeable: import("@sigx/runtime-core").ComponentFactory<SwipeableProps, void, {
|
|
38
|
+
default: () => import("@sigx/runtime-core").JSXElement | import("@sigx/runtime-core").JSXElement[] | null;
|
|
39
|
+
}>;
|
|
40
|
+
//# sourceMappingURL=Swipeable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Swipeable.d.ts","sourceRoot":"","sources":["../../src/components/Swipeable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,MAAM,EAEZ,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzC,MAAM,MAAM,cAAc,GACtB,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC9C,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,CAAC,GAC/C,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,CAAC,GAC3C,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1C,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,OAAO,EAAE,KAAK,CAAC,GAChD,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,OAAO,EAAE,KAAK,CAAC,GACjD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,GACnC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GAC5D,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,CAAC,GACtE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GACtB,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC9C,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;AASrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,SAAS;;EAwJpB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { usePinch } from './use-pinch.js';
|
|
2
|
+
export { useRotation } from './use-rotation.js';
|
|
3
|
+
export { useSharedValue, SharedValue, useAnimatedValue, AnimatedValue, useAnimatedStyle, resetAnimatedStyleBindingIds, } from '@sigx/lynx';
|
|
4
|
+
export type { SharedValueState, AnimatedValueState, BuiltinMapperName, MapperParams, } from '@sigx/lynx';
|
|
5
|
+
export { Pressable } from './components/Pressable.js';
|
|
6
|
+
export type { PressableProps } from './components/Pressable.js';
|
|
7
|
+
export { Draggable } from './components/Draggable.js';
|
|
8
|
+
export type { DraggableProps, DragEndDetail } from './components/Draggable.js';
|
|
9
|
+
export { Swipeable } from './components/Swipeable.js';
|
|
10
|
+
export type { SwipeableProps, SwipeSide } from './components/Swipeable.js';
|
|
11
|
+
export { ScrollView } from './components/ScrollView.js';
|
|
12
|
+
export type { ScrollViewProps } from './components/ScrollView.js';
|
|
13
|
+
export { useScrollContext } from './scroll-context.js';
|
|
14
|
+
export type { ScrollContext } from './scroll-context.js';
|
|
15
|
+
export type { TouchPoint, TouchEvent, GesturePhase, GestureHandlers, PinchState, UsePinchOptions, UsePinchReturn, RotationState, UseRotationOptions, UseRotationReturn, } from './types.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAOhD,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,2BAA2B,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAKlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|