react-native-chess-kit 0.1.0 → 0.2.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/lib/commonjs/board-annotations.js +131 -0
- package/lib/commonjs/board-annotations.js.map +1 -0
- package/lib/commonjs/board-arrows.js +164 -0
- package/lib/commonjs/board-arrows.js.map +1 -0
- package/lib/commonjs/board-highlights.js +212 -0
- package/lib/commonjs/board-highlights.js.map +1 -0
- package/lib/commonjs/board-piece.js +41 -12
- package/lib/commonjs/board-piece.js.map +1 -1
- package/lib/commonjs/board-pieces.js +2 -0
- package/lib/commonjs/board-pieces.js.map +1 -1
- package/lib/commonjs/board.js +392 -42
- package/lib/commonjs/board.js.map +1 -1
- package/lib/commonjs/constants.js +104 -0
- package/lib/commonjs/constants.js.map +1 -0
- package/lib/commonjs/index.js +128 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/pieces/default-pieces.js +536 -0
- package/lib/commonjs/pieces/default-pieces.js.map +1 -0
- package/lib/commonjs/pieces/index.js +13 -0
- package/lib/commonjs/pieces/index.js.map +1 -0
- package/lib/commonjs/promotion-picker.js +129 -0
- package/lib/commonjs/promotion-picker.js.map +1 -0
- package/lib/commonjs/static-board.js +150 -0
- package/lib/commonjs/static-board.js.map +1 -0
- package/lib/commonjs/themes.js +175 -0
- package/lib/commonjs/themes.js.map +1 -0
- package/lib/commonjs/use-board-gesture.js +184 -11
- package/lib/commonjs/use-board-gesture.js.map +1 -1
- package/lib/commonjs/use-premove.js +44 -0
- package/lib/commonjs/use-premove.js.map +1 -0
- package/lib/module/board-annotations.js +126 -0
- package/lib/module/board-annotations.js.map +1 -0
- package/lib/module/board-arrows.js +161 -0
- package/lib/module/board-arrows.js.map +1 -0
- package/lib/module/board-highlights.js +206 -0
- package/lib/module/board-highlights.js.map +1 -0
- package/lib/module/board-piece.js +42 -13
- package/lib/module/board-piece.js.map +1 -1
- package/lib/module/board-pieces.js +2 -0
- package/lib/module/board-pieces.js.map +1 -1
- package/lib/module/board.js +395 -44
- package/lib/module/board.js.map +1 -1
- package/lib/module/constants.js +100 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +29 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/pieces/default-pieces.js +530 -0
- package/lib/module/pieces/default-pieces.js.map +1 -0
- package/lib/module/pieces/index.js +4 -0
- package/lib/module/pieces/index.js.map +1 -0
- package/lib/module/promotion-picker.js +124 -0
- package/lib/module/promotion-picker.js.map +1 -0
- package/lib/module/static-board.js +146 -0
- package/lib/module/static-board.js.map +1 -0
- package/lib/module/themes.js +171 -0
- package/lib/module/themes.js.map +1 -0
- package/lib/module/use-board-gesture.js +185 -11
- package/lib/module/use-board-gesture.js.map +1 -1
- package/lib/module/use-premove.js +40 -0
- package/lib/module/use-premove.js.map +1 -0
- package/lib/typescript/board-annotations.d.ts +30 -0
- package/lib/typescript/board-annotations.d.ts.map +1 -0
- package/lib/typescript/board-arrows.d.ts +27 -0
- package/lib/typescript/board-arrows.d.ts.map +1 -0
- package/lib/typescript/board-highlights.d.ts +65 -0
- package/lib/typescript/board-highlights.d.ts.map +1 -0
- package/lib/typescript/board-piece.d.ts +10 -4
- package/lib/typescript/board-piece.d.ts.map +1 -1
- package/lib/typescript/board-pieces.d.ts +2 -1
- package/lib/typescript/board-pieces.d.ts.map +1 -1
- package/lib/typescript/board.d.ts +11 -2
- package/lib/typescript/board.d.ts.map +1 -1
- package/lib/typescript/constants.d.ts +54 -0
- package/lib/typescript/constants.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/pieces/default-pieces.d.ts +3 -0
- package/lib/typescript/pieces/default-pieces.d.ts.map +1 -0
- package/lib/typescript/pieces/index.d.ts +2 -0
- package/lib/typescript/pieces/index.d.ts.map +1 -0
- package/lib/typescript/promotion-picker.d.ts +30 -0
- package/lib/typescript/promotion-picker.d.ts.map +1 -0
- package/lib/typescript/static-board.d.ts +12 -0
- package/lib/typescript/static-board.d.ts.map +1 -0
- package/lib/typescript/themes.d.ts +15 -0
- package/lib/typescript/themes.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +194 -24
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/use-board-gesture.d.ts +28 -2
- package/lib/typescript/use-board-gesture.d.ts.map +1 -1
- package/lib/typescript/use-premove.d.ts +31 -0
- package/lib/typescript/use-premove.d.ts.map +1 -0
- package/package.json +4 -2
- package/src/board-annotations.tsx +147 -0
- package/src/board-arrows.tsx +197 -0
- package/src/board-highlights.tsx +226 -0
- package/src/board-piece.tsx +50 -12
- package/src/board-pieces.tsx +4 -1
- package/src/board.tsx +462 -46
- package/src/constants.ts +100 -0
- package/src/index.ts +62 -1
- package/src/pieces/default-pieces.tsx +383 -0
- package/src/pieces/index.ts +1 -0
- package/src/promotion-picker.tsx +147 -0
- package/src/static-board.tsx +150 -0
- package/src/themes.ts +129 -0
- package/src/types.ts +251 -25
- package/src/use-board-gesture.ts +219 -8
- package/src/use-premove.ts +59 -0
package/src/use-board-gesture.ts
CHANGED
|
@@ -5,15 +5,47 @@ import {
|
|
|
5
5
|
runOnJS,
|
|
6
6
|
} from 'react-native-reanimated';
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
ChessColor,
|
|
10
|
+
MoveMethod,
|
|
11
|
+
BoardPiece,
|
|
12
|
+
LegalMoveTarget,
|
|
13
|
+
GestureState,
|
|
14
|
+
PieceCode,
|
|
15
|
+
HapticType,
|
|
16
|
+
PremoveData,
|
|
17
|
+
} from './types';
|
|
9
18
|
import { xyToSquare } from './use-board-pieces';
|
|
10
19
|
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Callback types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
11
24
|
type GestureCallbacks = {
|
|
12
25
|
onPieceSelected: (square: string) => void;
|
|
13
26
|
onPieceMoved: (from: string, to: string) => void;
|
|
14
27
|
onSelectionCleared: () => void;
|
|
15
28
|
};
|
|
16
29
|
|
|
30
|
+
/** Rich callbacks exposed to consumers (all optional) */
|
|
31
|
+
type RichCallbacks = {
|
|
32
|
+
onPieceClick?: (square: string, piece: PieceCode) => void;
|
|
33
|
+
onSquareClick?: (square: string) => void;
|
|
34
|
+
onPieceDragBegin?: (square: string, piece: PieceCode) => void;
|
|
35
|
+
onPieceDragEnd?: (square: string, piece: PieceCode) => void;
|
|
36
|
+
onSquareLongPress?: (square: string) => void;
|
|
37
|
+
onHaptic?: (type: HapticType) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Premove-related callbacks from board.tsx */
|
|
41
|
+
type PremoveCallbacks = {
|
|
42
|
+
onPremoveSet?: (premove: PremoveData) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Params
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
17
49
|
type UseBoardGestureParams = {
|
|
18
50
|
squareSize: number;
|
|
19
51
|
orientation: ChessColor;
|
|
@@ -22,10 +54,16 @@ type UseBoardGestureParams = {
|
|
|
22
54
|
moveMethod: MoveMethod;
|
|
23
55
|
pieces: BoardPiece[];
|
|
24
56
|
callbacks: GestureCallbacks;
|
|
57
|
+
richCallbacks?: RichCallbacks;
|
|
58
|
+
premoveCallbacks?: PremoveCallbacks;
|
|
59
|
+
/** Whether premoves are enabled */
|
|
60
|
+
premovesEnabled?: boolean;
|
|
25
61
|
/** Currently selected square (for tap-to-move second tap) */
|
|
26
62
|
selectedSquare: string | null;
|
|
27
63
|
/** Legal move targets from the currently selected piece */
|
|
28
64
|
legalMoves: LegalMoveTarget[];
|
|
65
|
+
/** Whose turn it is ('w' or 'b') — used for premove detection */
|
|
66
|
+
currentTurn?: 'w' | 'b';
|
|
29
67
|
};
|
|
30
68
|
|
|
31
69
|
type UseBoardGestureReturn = {
|
|
@@ -33,6 +71,24 @@ type UseBoardGestureReturn = {
|
|
|
33
71
|
gestureState: GestureState;
|
|
34
72
|
};
|
|
35
73
|
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Helpers
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/** Check if a piece color matches the current player turn */
|
|
79
|
+
function isPieceTurn(pieceColor: 'w' | 'b', currentTurn: 'w' | 'b'): boolean {
|
|
80
|
+
return pieceColor === currentTurn;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Map piece color char to player ChessColor */
|
|
84
|
+
function pieceColorToPlayer(color: 'w' | 'b'): ChessColor {
|
|
85
|
+
return color === 'w' ? 'white' : 'black';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Hook
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
36
92
|
/**
|
|
37
93
|
* Single centralized gesture handler for the entire board.
|
|
38
94
|
*
|
|
@@ -51,6 +107,13 @@ type UseBoardGestureReturn = {
|
|
|
51
107
|
* gestureEnabled, player, or moveMethod change). Frequently-changing data
|
|
52
108
|
* (pieces, selectedSquare, legalMoves) is read from refs via runOnJS bridge
|
|
53
109
|
* functions, avoiding costly gesture teardown/rebuild on every move.
|
|
110
|
+
*
|
|
111
|
+
* v0.2.0 additions:
|
|
112
|
+
* - Rich callbacks (onPieceClick, onSquareClick, onPieceDragBegin, onPieceDragEnd)
|
|
113
|
+
* - Drag target square tracking (shared value for DragTargetHighlight)
|
|
114
|
+
* - Premove support (queue move when not your turn)
|
|
115
|
+
* - Haptic feedback via callback
|
|
116
|
+
* - Long press detection for onSquareLongPress
|
|
54
117
|
*/
|
|
55
118
|
export function useBoardGesture({
|
|
56
119
|
squareSize,
|
|
@@ -60,8 +123,12 @@ export function useBoardGesture({
|
|
|
60
123
|
moveMethod,
|
|
61
124
|
pieces,
|
|
62
125
|
callbacks,
|
|
126
|
+
richCallbacks,
|
|
127
|
+
premoveCallbacks,
|
|
128
|
+
premovesEnabled = false,
|
|
63
129
|
selectedSquare,
|
|
64
130
|
legalMoves,
|
|
131
|
+
currentTurn,
|
|
65
132
|
}: UseBoardGestureParams): UseBoardGestureReturn {
|
|
66
133
|
// Shared values for drag tracking — updated on UI thread only
|
|
67
134
|
const activeSquare = useSharedValue<string | null>(null);
|
|
@@ -69,6 +136,7 @@ export function useBoardGesture({
|
|
|
69
136
|
const dragY = useSharedValue(0);
|
|
70
137
|
const isDragging = useSharedValue(false);
|
|
71
138
|
const dragPieceCode = useSharedValue<string | null>(null);
|
|
139
|
+
const dragTargetSquare = useSharedValue<string | null>(null);
|
|
72
140
|
|
|
73
141
|
const gestureState: GestureState = {
|
|
74
142
|
activeSquare,
|
|
@@ -76,6 +144,7 @@ export function useBoardGesture({
|
|
|
76
144
|
dragY,
|
|
77
145
|
isDragging,
|
|
78
146
|
dragPieceCode,
|
|
147
|
+
dragTargetSquare,
|
|
79
148
|
};
|
|
80
149
|
|
|
81
150
|
// --- Refs for frequently-changing data (read from JS thread via runOnJS) ---
|
|
@@ -92,6 +161,18 @@ export function useBoardGesture({
|
|
|
92
161
|
const callbacksRef = useRef(callbacks);
|
|
93
162
|
callbacksRef.current = callbacks;
|
|
94
163
|
|
|
164
|
+
const richCallbacksRef = useRef(richCallbacks);
|
|
165
|
+
richCallbacksRef.current = richCallbacks;
|
|
166
|
+
|
|
167
|
+
const premoveCallbacksRef = useRef(premoveCallbacks);
|
|
168
|
+
premoveCallbacksRef.current = premoveCallbacks;
|
|
169
|
+
|
|
170
|
+
const currentTurnRef = useRef(currentTurn);
|
|
171
|
+
currentTurnRef.current = currentTurn;
|
|
172
|
+
|
|
173
|
+
// Track the piece being dragged for rich drag-end callback
|
|
174
|
+
const draggedPieceRef = useRef<{ square: string; code: PieceCode } | null>(null);
|
|
175
|
+
|
|
95
176
|
// --- JS-thread bridge functions called from worklets via runOnJS ---
|
|
96
177
|
// These read current values from refs, so they always have fresh data.
|
|
97
178
|
|
|
@@ -101,14 +182,21 @@ export function useBoardGesture({
|
|
|
101
182
|
const currentSelected = selectedSquareRef.current;
|
|
102
183
|
const currentLegalMoves = legalMovesRef.current;
|
|
103
184
|
const cbs = callbacksRef.current;
|
|
185
|
+
const rich = richCallbacksRef.current;
|
|
104
186
|
const canClick = moveMethod !== 'drag';
|
|
105
187
|
|
|
106
188
|
// Build lookup for the current touch
|
|
107
189
|
const piece = currentPieces.find((p) => p.square === square);
|
|
108
190
|
const isPlayerPiece = piece
|
|
109
|
-
? player === 'both' || (piece.color
|
|
191
|
+
? player === 'both' || pieceColorToPlayer(piece.color) === player
|
|
110
192
|
: false;
|
|
111
193
|
|
|
194
|
+
// Check if it's this piece's turn (for premove detection)
|
|
195
|
+
const turn = currentTurnRef.current;
|
|
196
|
+
const isOwnTurn = piece && turn
|
|
197
|
+
? isPieceTurn(piece.color, turn)
|
|
198
|
+
: true; // default to true if turn not tracked
|
|
199
|
+
|
|
112
200
|
// Click-to-move: second tap on a legal target square
|
|
113
201
|
const legalSquares = new Set(currentLegalMoves.map((m) => m.square));
|
|
114
202
|
if (canClick && currentSelected && legalSquares.has(square)) {
|
|
@@ -116,25 +204,93 @@ export function useBoardGesture({
|
|
|
116
204
|
activeSquare.value = null;
|
|
117
205
|
isDragging.value = false;
|
|
118
206
|
dragPieceCode.value = null;
|
|
207
|
+
dragTargetSquare.value = null;
|
|
208
|
+
rich?.onHaptic?.('move');
|
|
119
209
|
return;
|
|
120
210
|
}
|
|
121
211
|
|
|
122
212
|
if (isPlayerPiece && piece) {
|
|
123
|
-
//
|
|
213
|
+
// Premove: player piece but not their turn
|
|
214
|
+
if (premovesEnabled && !isOwnTurn) {
|
|
215
|
+
// If there's already a selected square, this tap completes a premove
|
|
216
|
+
if (currentSelected && currentSelected !== square) {
|
|
217
|
+
premoveCallbacksRef.current?.onPremoveSet?.({
|
|
218
|
+
from: currentSelected,
|
|
219
|
+
to: square,
|
|
220
|
+
});
|
|
221
|
+
cbs.onSelectionCleared();
|
|
222
|
+
activeSquare.value = null;
|
|
223
|
+
dragPieceCode.value = null;
|
|
224
|
+
dragTargetSquare.value = null;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// First tap: select the piece for premove
|
|
228
|
+
activeSquare.value = square;
|
|
229
|
+
dragX.value = touchX;
|
|
230
|
+
dragY.value = touchY;
|
|
231
|
+
dragPieceCode.value = piece.code;
|
|
232
|
+
cbs.onPieceSelected(square);
|
|
233
|
+
rich?.onPieceClick?.(square, piece.code as PieceCode);
|
|
234
|
+
rich?.onHaptic?.('select');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Normal case: tapped/started dragging a player piece on their turn
|
|
124
239
|
activeSquare.value = square;
|
|
125
240
|
dragX.value = touchX;
|
|
126
241
|
dragY.value = touchY;
|
|
127
242
|
dragPieceCode.value = piece.code;
|
|
243
|
+
draggedPieceRef.current = { square, code: piece.code as PieceCode };
|
|
128
244
|
cbs.onPieceSelected(square);
|
|
245
|
+
|
|
246
|
+
// Rich callbacks
|
|
247
|
+
rich?.onPieceClick?.(square, piece.code as PieceCode);
|
|
248
|
+
rich?.onHaptic?.('select');
|
|
129
249
|
} else {
|
|
130
|
-
// Tapped empty square or opponent piece
|
|
250
|
+
// Tapped empty square or opponent piece
|
|
251
|
+
|
|
252
|
+
// If premoves enabled and there's a selection, check if this is a premove target
|
|
253
|
+
if (premovesEnabled && currentSelected && !isOwnTurn) {
|
|
254
|
+
premoveCallbacksRef.current?.onPremoveSet?.({
|
|
255
|
+
from: currentSelected,
|
|
256
|
+
to: square,
|
|
257
|
+
});
|
|
258
|
+
cbs.onSelectionCleared();
|
|
259
|
+
activeSquare.value = null;
|
|
260
|
+
dragPieceCode.value = null;
|
|
261
|
+
dragTargetSquare.value = null;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
131
265
|
activeSquare.value = null;
|
|
132
266
|
dragPieceCode.value = null;
|
|
267
|
+
dragTargetSquare.value = null;
|
|
133
268
|
if (currentSelected) {
|
|
134
269
|
cbs.onSelectionCleared();
|
|
135
270
|
}
|
|
271
|
+
|
|
272
|
+
// Rich callback: square click (empty square or opponent piece)
|
|
273
|
+
rich?.onSquareClick?.(square);
|
|
136
274
|
}
|
|
137
|
-
}, [squareSize, orientation, player, moveMethod, activeSquare, dragX, dragY, isDragging, dragPieceCode]);
|
|
275
|
+
}, [squareSize, orientation, player, moveMethod, premovesEnabled, activeSquare, dragX, dragY, isDragging, dragPieceCode, dragTargetSquare]);
|
|
276
|
+
|
|
277
|
+
const handleDragStart = useCallback((touchX: number, touchY: number) => {
|
|
278
|
+
const rich = richCallbacksRef.current;
|
|
279
|
+
const dragged = draggedPieceRef.current;
|
|
280
|
+
if (dragged) {
|
|
281
|
+
rich?.onPieceDragBegin?.(dragged.square, dragged.code);
|
|
282
|
+
}
|
|
283
|
+
// Update drag target to current square
|
|
284
|
+
const square = xyToSquare(touchX, touchY, squareSize, orientation);
|
|
285
|
+
dragTargetSquare.value = square;
|
|
286
|
+
}, [squareSize, orientation, dragTargetSquare]);
|
|
287
|
+
|
|
288
|
+
const handleDragUpdate = useCallback((touchX: number, touchY: number) => {
|
|
289
|
+
// Update drag target square (for highlight). This runs on JS thread
|
|
290
|
+
// but is called from worklet via runOnJS only when the square changes.
|
|
291
|
+
const square = xyToSquare(touchX, touchY, squareSize, orientation);
|
|
292
|
+
dragTargetSquare.value = square;
|
|
293
|
+
}, [squareSize, orientation, dragTargetSquare]);
|
|
138
294
|
|
|
139
295
|
const handleEnd = useCallback((touchX: number, touchY: number) => {
|
|
140
296
|
const fromSquare = activeSquare.value;
|
|
@@ -142,18 +298,50 @@ export function useBoardGesture({
|
|
|
142
298
|
|
|
143
299
|
const toSquare = xyToSquare(touchX, touchY, squareSize, orientation);
|
|
144
300
|
isDragging.value = false;
|
|
301
|
+
dragTargetSquare.value = null;
|
|
302
|
+
|
|
303
|
+
const rich = richCallbacksRef.current;
|
|
304
|
+
|
|
305
|
+
// Fire drag end callback
|
|
306
|
+
const dragged = draggedPieceRef.current;
|
|
307
|
+
if (dragged) {
|
|
308
|
+
rich?.onPieceDragEnd?.(toSquare, dragged.code);
|
|
309
|
+
draggedPieceRef.current = null;
|
|
310
|
+
}
|
|
145
311
|
|
|
146
312
|
if (fromSquare !== toSquare) {
|
|
147
|
-
|
|
313
|
+
// Check if this is a premove (not your turn)
|
|
314
|
+
const turn = currentTurnRef.current;
|
|
315
|
+
const piece = piecesRef.current.find((p) => p.square === fromSquare);
|
|
316
|
+
const isOwnTurn = piece && turn ? isPieceTurn(piece.color, turn) : true;
|
|
317
|
+
|
|
318
|
+
if (premovesEnabled && !isOwnTurn) {
|
|
319
|
+
premoveCallbacksRef.current?.onPremoveSet?.({
|
|
320
|
+
from: fromSquare,
|
|
321
|
+
to: toSquare,
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
callbacksRef.current.onPieceMoved(fromSquare, toSquare);
|
|
325
|
+
}
|
|
148
326
|
}
|
|
149
327
|
|
|
150
328
|
activeSquare.value = null;
|
|
151
329
|
dragPieceCode.value = null;
|
|
152
|
-
}, [squareSize, orientation, activeSquare, isDragging, dragPieceCode]);
|
|
330
|
+
}, [squareSize, orientation, activeSquare, isDragging, dragPieceCode, dragTargetSquare, premovesEnabled]);
|
|
331
|
+
|
|
332
|
+
// Long press handler (separate gesture, composed with pan)
|
|
333
|
+
const handleLongPress = useCallback((touchX: number, touchY: number) => {
|
|
334
|
+
const square = xyToSquare(touchX, touchY, squareSize, orientation);
|
|
335
|
+
richCallbacksRef.current?.onSquareLongPress?.(square);
|
|
336
|
+
}, [squareSize, orientation]);
|
|
153
337
|
|
|
154
338
|
// --- Build the gesture (STABLE — only changes on layout/config changes) ---
|
|
155
339
|
const canDrag = moveMethod !== 'click';
|
|
156
340
|
|
|
341
|
+
// Track the last drag target square to avoid redundant runOnJS calls
|
|
342
|
+
const lastDragTargetCol = useSharedValue(-1);
|
|
343
|
+
const lastDragTargetRow = useSharedValue(-1);
|
|
344
|
+
|
|
157
345
|
const gesture = useMemo(() => {
|
|
158
346
|
return Gesture.Pan()
|
|
159
347
|
.enabled(gestureEnabled)
|
|
@@ -163,10 +351,11 @@ export function useBoardGesture({
|
|
|
163
351
|
// Bridge to JS for piece lookup + selection logic
|
|
164
352
|
runOnJS(handleBegin)(e.x, e.y);
|
|
165
353
|
})
|
|
166
|
-
.onStart(() => {
|
|
354
|
+
.onStart((e) => {
|
|
167
355
|
'worklet';
|
|
168
356
|
if (!canDrag || !activeSquare.value) return;
|
|
169
357
|
isDragging.value = true;
|
|
358
|
+
runOnJS(handleDragStart)(e.x, e.y);
|
|
170
359
|
})
|
|
171
360
|
.onUpdate((e) => {
|
|
172
361
|
'worklet';
|
|
@@ -174,6 +363,15 @@ export function useBoardGesture({
|
|
|
174
363
|
// Only 2 shared value writes — no JS bridge, no re-renders
|
|
175
364
|
dragX.value = e.x;
|
|
176
365
|
dragY.value = e.y;
|
|
366
|
+
|
|
367
|
+
// Update drag target square (only when square changes to minimize JS bridge calls)
|
|
368
|
+
const col = Math.max(0, Math.min(7, Math.floor(e.x / squareSize)));
|
|
369
|
+
const row = Math.max(0, Math.min(7, Math.floor(e.y / squareSize)));
|
|
370
|
+
if (col !== lastDragTargetCol.value || row !== lastDragTargetRow.value) {
|
|
371
|
+
lastDragTargetCol.value = col;
|
|
372
|
+
lastDragTargetRow.value = row;
|
|
373
|
+
runOnJS(handleDragUpdate)(e.x, e.y);
|
|
374
|
+
}
|
|
177
375
|
})
|
|
178
376
|
.onEnd((e) => {
|
|
179
377
|
'worklet';
|
|
@@ -184,18 +382,31 @@ export function useBoardGesture({
|
|
|
184
382
|
'worklet';
|
|
185
383
|
// Safety reset if gesture was interrupted
|
|
186
384
|
isDragging.value = false;
|
|
385
|
+
dragTargetSquare.value = null;
|
|
386
|
+
lastDragTargetCol.value = -1;
|
|
387
|
+
lastDragTargetRow.value = -1;
|
|
187
388
|
});
|
|
188
389
|
}, [
|
|
189
390
|
gestureEnabled,
|
|
190
391
|
canDrag,
|
|
392
|
+
squareSize,
|
|
191
393
|
handleBegin,
|
|
394
|
+
handleDragStart,
|
|
395
|
+
handleDragUpdate,
|
|
192
396
|
handleEnd,
|
|
193
397
|
// Shared values are stable refs — listed for exhaustive-deps but don't cause recreations
|
|
194
398
|
activeSquare,
|
|
195
399
|
dragX,
|
|
196
400
|
dragY,
|
|
197
401
|
isDragging,
|
|
402
|
+
dragTargetSquare,
|
|
403
|
+
lastDragTargetCol,
|
|
404
|
+
lastDragTargetRow,
|
|
198
405
|
]);
|
|
199
406
|
|
|
407
|
+
// Compose with long press if the consumer wants it
|
|
408
|
+
// For now, long press is detected via a separate gesture that runs simultaneously.
|
|
409
|
+
// This is done at the board level if needed — keeping the pan gesture clean here.
|
|
410
|
+
|
|
200
411
|
return { gesture, gestureState };
|
|
201
412
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { PremoveData } from './types';
|
|
4
|
+
|
|
5
|
+
type UsePremoveReturn = {
|
|
6
|
+
/** The currently queued premove (only one at a time, like lichess) */
|
|
7
|
+
premove: PremoveData | null;
|
|
8
|
+
/** Queue a premove. Replaces any existing premove. */
|
|
9
|
+
setPremove: (premove: PremoveData) => void;
|
|
10
|
+
/** Clear the queued premove */
|
|
11
|
+
clearPremove: () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Try to execute the queued premove.
|
|
14
|
+
* Returns the premove if one was queued (so the caller can apply it),
|
|
15
|
+
* then clears the queue. Returns null if no premove was queued.
|
|
16
|
+
*/
|
|
17
|
+
consumePremove: () => PremoveData | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manages a single premove queue (one premove at a time).
|
|
22
|
+
*
|
|
23
|
+
* Premoves work like lichess:
|
|
24
|
+
* 1. When it's not your turn, you can drag/click a move — it queues as a premove
|
|
25
|
+
* 2. The premove squares are highlighted with a distinct color
|
|
26
|
+
* 3. When your turn begins (FEN changes and it's now your turn), the premove
|
|
27
|
+
* is automatically attempted via the board's internal chess.js
|
|
28
|
+
* 4. If the premove is legal, it's applied; if not, it's silently discarded
|
|
29
|
+
*
|
|
30
|
+
* The board.tsx orchestrates this: it calls consumePremove() in a useEffect
|
|
31
|
+
* that watches the FEN (turn change), then attempts the move.
|
|
32
|
+
*/
|
|
33
|
+
export function usePremove(): UsePremoveReturn {
|
|
34
|
+
const [premove, setPremoveState] = useState<PremoveData | null>(null);
|
|
35
|
+
|
|
36
|
+
const setPremove = useCallback((pm: PremoveData) => {
|
|
37
|
+
setPremoveState(pm);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const clearPremove = useCallback(() => {
|
|
41
|
+
setPremoveState(null);
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const consumePremove = useCallback((): PremoveData | null => {
|
|
45
|
+
let consumed: PremoveData | null = null;
|
|
46
|
+
setPremoveState((current) => {
|
|
47
|
+
consumed = current;
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
return consumed;
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
premove,
|
|
55
|
+
setPremove,
|
|
56
|
+
clearPremove,
|
|
57
|
+
consumePremove,
|
|
58
|
+
};
|
|
59
|
+
}
|