Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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