Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/all-crabs-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lynx-js/testing-environment': patch
---

Add `__RemoveGestureDetector` PAPI binding
5 changes: 5 additions & 0 deletions .changeset/fair-horses-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lynx-js/react': patch
---

Remove stale gestures when gestures are removed
26 changes: 26 additions & 0 deletions examples/gesture/lynx.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { pluginQRCode } from '@lynx-js/qrcode-rsbuild-plugin';
import { pluginReactLynx } from '@lynx-js/react-rsbuild-plugin';
import { defineConfig } from '@lynx-js/rspeedy';

const enableBundleAnalysis = !!process.env['RSPEEDY_BUNDLE_ANALYSIS'];

export default defineConfig({
plugins: [
pluginReactLynx({
enableNewGesture: true,
}),
pluginQRCode({
schema(url) {
return `${url}?fullscreen=true`;
},
}),
],
environments: {
web: {},
lynx: {
performance: {
profile: enableBundleAnalysis,
},
},
},
});
23 changes: 23 additions & 0 deletions examples/gesture/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@lynx-js/example-gesture",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "rspeedy build",
"dev": "rspeedy dev",
"test:type": "tsc --noEmit"
},
"dependencies": {
"@lynx-js/gesture-runtime": "workspace:*",
"@lynx-js/react": "workspace:*"
},
"devDependencies": {
"@lynx-js/preact-devtools": "^5.0.1",
"@lynx-js/qrcode-rsbuild-plugin": "workspace:*",
"@lynx-js/react-rsbuild-plugin": "workspace:*",
"@lynx-js/rspeedy": "workspace:*",
"@lynx-js/types": "3.7.0",
"@types/react": "^18.3.28"
}
}
105 changes: 105 additions & 0 deletions examples/gesture/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
:root {
--bg: #0a1022;
--card: #141e3d;
--card-border: #2a3765;
--text: #eaf0ff;
--text-dim: #9fb0de;
--button: #22315f;
--button-active: #3c5df6;
--box-a: #ff8b3d;
--box-b: #28c6a5;
--box-off: #6f7b9f;
}

page {
background-color: var(--bg);
}

.Root {
min-height: 100vh;
padding: 36rpx 24rpx;
box-sizing: border-box;
align-items: center;
justify-content: center;
}

.Card {
width: 100%;
max-width: 680rpx;
padding: 28rpx;
border-radius: 24rpx;
background: var(--card);
border: 2rpx solid var(--card-border);
gap: 20rpx;
}

.Title {
color: var(--text);
font-size: 44rpx;
font-weight: 700;
}

.Description {
color: var(--text-dim);
font-size: 28rpx;
line-height: 38rpx;
}

.Controls {
flex-direction: column;
gap: 14rpx;
}

.Button {
border-radius: 16rpx;
background: var(--button);
padding: 16rpx 20rpx;
}

.Button--active {
background: var(--button-active);
}

.ButtonText {
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
}

.ModeText {
color: var(--text);
font-size: 27rpx;
}

.Stage {
margin-top: 8rpx;
height: 560rpx;
border-radius: 18rpx;
border: 2rpx dashed #4e5e97;
align-items: center;
justify-content: center;
overflow: hidden;
}

.DragBox {
width: 160rpx;
height: 160rpx;
border-radius: 24rpx;
background: var(--box-a);
align-items: center;
justify-content: center;
}

.DragBox--diff {
background: var(--box-b);
}

.DragBox--removed {
background: var(--box-off);
}

.DragBoxText {
color: #111827;
font-size: 34rpx;
font-weight: 800;
}
171 changes: 171 additions & 0 deletions examples/gesture/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { PanGesture, useGesture } from '@lynx-js/gesture-runtime';
import { useCallback, useMainThreadRef, useState } from '@lynx-js/react';

import './App.css';

type GestureMode = 'set' | 'diff' | 'remove';

interface Point {
x: number;
y: number;
}

export function App() {
const [mode, setMode] = useState<GestureMode>('set');
const startPointMTRef = useMainThreadRef<Point>({ x: 0, y: 0 });
const baseOffsetMTRef = useMainThreadRef<Point>({ x: 0, y: 0 });

const panGestureA = useGesture(PanGesture);
panGestureA
.onBegin((event) => {
'main thread';
startPointMTRef.current = {
x: event.params.clientX,
y: event.params.clientY,
};
event.currentTarget.setStyleProperty('opacity', '0.82');
console.info('PanGestureA onBegin', startPointMTRef.current);
})
.onUpdate((event) => {
'main thread';
const dx = event.params.clientX - startPointMTRef.current.x;
const dy = event.params.clientY - startPointMTRef.current.y;
const nextX = baseOffsetMTRef.current.x + dx;
const nextY = baseOffsetMTRef.current.y + dy;
event.currentTarget.setStyleProperty(
'transform',
`translate(${nextX}px, ${nextY}px)`,
);
console.info('PanGestureA onUpdate', dx, dy, nextX, nextY);
})
.onEnd((event) => {
'main thread';
const dx = event.params.clientX - startPointMTRef.current.x;
const dy = event.params.clientY - startPointMTRef.current.y;
baseOffsetMTRef.current = {
x: baseOffsetMTRef.current.x + dx,
y: baseOffsetMTRef.current.y + dy,
};
event.currentTarget.setStyleProperty('opacity', '1');
console.info('PanGestureA onEnd', baseOffsetMTRef.current);
});

const panGestureB = useGesture(PanGesture);
panGestureB
.onBegin((event) => {
'main thread';
startPointMTRef.current = {
x: event.params.clientX,
y: event.params.clientY,
};
event.currentTarget.setStyleProperty('opacity', '0.82');
console.info('PanGestureB onBegin', startPointMTRef.current);
})
.onUpdate((event) => {
'main thread';

const dx = event.params.clientX - startPointMTRef.current.x;
const nextX = baseOffsetMTRef.current.x + dx;
event.currentTarget.setStyleProperty(
'transform',
`translate(${nextX}px, 0px)`,
);
console.info('PanGestureB onUpdate', dx, nextX);
})
.onEnd((event) => {
'main thread';
const dx = event.params.clientX - startPointMTRef.current.x;
baseOffsetMTRef.current = {
x: baseOffsetMTRef.current.x + dx,
y: 0,
};
event.currentTarget.setStyleProperty('opacity', '1');
console.info('PanGestureB onEnd', baseOffsetMTRef.current);
});

const onSetGesture = useCallback(() => {
'background-only';
setMode('set');
}, []);

const onDiffGesture = useCallback(() => {
'background-only';
setMode('diff');
}, []);

const onRemoveGesture = useCallback(() => {
'background-only';
setMode('remove');
}, []);

const activeGesture = mode === 'set'
? panGestureA
: (mode === 'diff'
? panGestureB
: undefined);

const modeDescription = mode === 'set'
? 'Mode A: drag in both X and Y.'
: (mode === 'diff'
? 'Mode B: drag in X only.'
: 'Removed: drag should do nothing.');

const dragBoxGestureProps = activeGesture
? { 'main-thread:gesture': activeGesture }
: {};

return (
<view className='Root'>
<view className='Card'>
<text className='Title'>Gesture Lifecycle Playground</text>
<text className='Description'>
Tap buttons to trigger set/diff/remove and drag the square.
</text>
<text className='Description'>
Verify remove semantics: after tapping "Remove Gesture", dragging
should produce no PanGesture logs.
</text>
<text className='Description'>
Verify update semantics: after tapping "Diff to Gesture B", only
PanGestureB logs should appear.
</text>

<view className='Controls'>
<view
className={`Button ${mode === 'set' ? 'Button--active' : ''}`}
bindtap={onSetGesture}
>
<text className='ButtonText'>Set Gesture A</text>
</view>
<view
className={`Button ${mode === 'diff' ? 'Button--active' : ''}`}
bindtap={onDiffGesture}
>
<text className='ButtonText'>Diff to Gesture B</text>
</view>
<view
className={`Button ${mode === 'remove' ? 'Button--active' : ''}`}
bindtap={onRemoveGesture}
>
<text className='ButtonText'>Remove Gesture</text>
</view>
</view>

<text className='ModeText'>{modeDescription}</text>

<view className='Stage'>
<view
className={`DragBox ${mode === 'diff' ? 'DragBox--diff' : ''} ${
mode === 'remove' ? 'DragBox--removed' : ''
}`}
{...dragBoxGestureProps}
>
<text className='DragBoxText'>
{mode === 'set' ? 'A' : (mode === 'diff' ? 'B' : 'OFF')}
</text>
</view>
</view>
</view>
</view>
);
}
13 changes: 13 additions & 0 deletions examples/gesture/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import '@lynx-js/preact-devtools';
import '@lynx-js/react/debug';
import { root } from '@lynx-js/react';

import { App } from './App.jsx';

root.render(
<App />,
);

if (import.meta.webpackHot) {
import.meta.webpackHot.accept();
}
1 change: 1 addition & 0 deletions examples/gesture/src/rspeedy-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@lynx-js/rspeedy/client" />
18 changes: 18 additions & 0 deletions examples/gesture/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@lynx-js/react",
"noEmit": true,
"allowJs": true,
"checkJs": true,
"isolatedDeclarations": false,
},
"include": ["src", "lynx.config.js"],
"references": [
{ "path": "../../packages/react/tsconfig.json" },
{ "path": "../../packages/rspeedy/core/tsconfig.build.json" },
{ "path": "../../packages/rspeedy/plugin-qrcode/tsconfig.build.json" },
{ "path": "../../packages/rspeedy/plugin-react/tsconfig.build.json" },
],
}
Loading
Loading