react-native-chess-kit 0.4.2 → 0.5.1
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 -21
- package/README.md +168 -168
- package/lib/commonjs/board-annotations.js +8 -8
- package/lib/commonjs/board-arrows.js +7 -7
- package/lib/commonjs/board-background.js +5 -5
- package/lib/commonjs/board-coordinates.js +78 -11
- package/lib/commonjs/board-coordinates.js.map +1 -1
- package/lib/commonjs/board-drag-ghost.js +10 -10
- package/lib/commonjs/board-highlights.js +15 -15
- package/lib/commonjs/board-legal-dots.js +5 -5
- package/lib/commonjs/board-piece.js +25 -25
- package/lib/commonjs/board-pieces.js +6 -6
- package/lib/commonjs/board.js +76 -35
- package/lib/commonjs/board.js.map +1 -1
- package/lib/commonjs/constants.js +4 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/promotion-picker.js +8 -8
- package/lib/commonjs/static-board.js +51 -16
- package/lib/commonjs/static-board.js.map +1 -1
- package/lib/commonjs/use-board-gesture.js +52 -33
- package/lib/commonjs/use-board-gesture.js.map +1 -1
- package/lib/commonjs/use-board-pieces.js +15 -15
- package/lib/commonjs/use-board-state.js +8 -8
- package/lib/commonjs/use-premove.js +12 -12
- package/lib/module/board-annotations.js +8 -8
- package/lib/module/board-arrows.js +7 -7
- package/lib/module/board-background.js +5 -5
- package/lib/module/board-coordinates.js +79 -12
- package/lib/module/board-coordinates.js.map +1 -1
- package/lib/module/board-drag-ghost.js +10 -10
- package/lib/module/board-highlights.js +15 -15
- package/lib/module/board-legal-dots.js +5 -5
- package/lib/module/board-piece.js +25 -25
- package/lib/module/board-pieces.js +6 -6
- package/lib/module/board.js +77 -36
- package/lib/module/board.js.map +1 -1
- package/lib/module/constants.js +3 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/promotion-picker.js +8 -8
- package/lib/module/static-board.js +52 -17
- package/lib/module/static-board.js.map +1 -1
- package/lib/module/use-board-gesture.js +52 -33
- package/lib/module/use-board-gesture.js.map +1 -1
- package/lib/module/use-board-pieces.js +15 -15
- package/lib/module/use-board-state.js +8 -8
- package/lib/module/use-premove.js +12 -12
- package/lib/typescript/board-coordinates.d.ts +10 -4
- package/lib/typescript/board-coordinates.d.ts.map +1 -1
- package/lib/typescript/board.d.ts.map +1 -1
- package/lib/typescript/constants.d.ts +2 -0
- package/lib/typescript/constants.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/static-board.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +23 -3
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/use-board-gesture.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/board-annotations.tsx +147 -147
- package/src/board-arrows.tsx +197 -197
- package/src/board-background.tsx +46 -46
- package/src/board-coordinates.tsx +192 -98
- package/src/board-drag-ghost.tsx +132 -132
- package/src/board-highlights.tsx +226 -226
- package/src/board-legal-dots.tsx +73 -73
- package/src/board-piece.tsx +160 -160
- package/src/board-pieces.tsx +63 -63
- package/src/board.tsx +685 -641
- package/src/constants.ts +103 -100
- package/src/index.ts +101 -100
- package/src/pieces/default-pieces.tsx +383 -383
- package/src/pieces/index.ts +1 -1
- package/src/promotion-picker.tsx +147 -147
- package/src/static-board.tsx +187 -150
- package/src/themes.ts +129 -129
- package/src/types.ts +373 -352
- package/src/use-board-gesture.ts +429 -412
- package/src/use-board-pieces.ts +158 -158
- package/src/use-board-state.ts +111 -111
- package/src/use-premove.ts +59 -59
package/src/pieces/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DefaultPieceSet } from './default-pieces';
|
|
1
|
+
export { DefaultPieceSet } from './default-pieces';
|
package/src/promotion-picker.tsx
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
2
|
-
import { View, Pressable } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import type { ChessColor, PromotionPiece, PieceSetMap } from './types';
|
|
5
|
-
import { squareToXY } from './use-board-pieces';
|
|
6
|
-
import { PROMOTION_PIECE_PADDING } from './constants';
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Props
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
type PromotionPickerProps = {
|
|
13
|
-
/** Square the pawn is promoting on */
|
|
14
|
-
square: string;
|
|
15
|
-
/** Color of the promoting pawn */
|
|
16
|
-
pieceColor: 'w' | 'b';
|
|
17
|
-
/** Board size in pixels */
|
|
18
|
-
boardSize: number;
|
|
19
|
-
squareSize: number;
|
|
20
|
-
orientation: ChessColor;
|
|
21
|
-
/** Piece renderer (from renderPiece prop or pieceSet) */
|
|
22
|
-
renderPiece: (code: string, size: number) => React.ReactElement;
|
|
23
|
-
/** Called when user picks a promotion piece */
|
|
24
|
-
onSelect: (piece: PromotionPiece) => void;
|
|
25
|
-
/** Called when user cancels (taps outside) */
|
|
26
|
-
onCancel: () => void;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Promotion pieces in order
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
const PROMOTION_PIECES: PromotionPiece[] = ['q', 'r', 'b', 'n'];
|
|
34
|
-
|
|
35
|
-
function promotionPieceCode(color: 'w' | 'b', piece: PromotionPiece): string {
|
|
36
|
-
return `${color}${piece}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Component
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Promotion piece picker overlay.
|
|
45
|
-
*
|
|
46
|
-
* Appears as a vertical column of 4 pieces (Q, R, B, N) anchored to the
|
|
47
|
-
* promotion square. Expands downward when promoting on the top edge,
|
|
48
|
-
* upward when promoting on the bottom edge.
|
|
49
|
-
*
|
|
50
|
-
* A semi-transparent backdrop covers the board. Tapping outside cancels.
|
|
51
|
-
*/
|
|
52
|
-
export const PromotionPicker = React.memo(function PromotionPicker({
|
|
53
|
-
square,
|
|
54
|
-
pieceColor,
|
|
55
|
-
boardSize,
|
|
56
|
-
squareSize,
|
|
57
|
-
orientation,
|
|
58
|
-
renderPiece,
|
|
59
|
-
onSelect,
|
|
60
|
-
onCancel,
|
|
61
|
-
}: PromotionPickerProps) {
|
|
62
|
-
const { x, y } = squareToXY(square, squareSize, orientation);
|
|
63
|
-
const pieceSize = squareSize * (1 - PROMOTION_PIECE_PADDING * 2);
|
|
64
|
-
const padding = squareSize * PROMOTION_PIECE_PADDING;
|
|
65
|
-
|
|
66
|
-
// Determine if the picker should expand downward or upward
|
|
67
|
-
// If the promotion square is in the top half, expand downward
|
|
68
|
-
const expandDown = y < boardSize / 2;
|
|
69
|
-
|
|
70
|
-
const handleSelect = useCallback(
|
|
71
|
-
(piece: PromotionPiece) => {
|
|
72
|
-
onSelect(piece);
|
|
73
|
-
},
|
|
74
|
-
[onSelect],
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<View
|
|
79
|
-
style={{
|
|
80
|
-
position: 'absolute',
|
|
81
|
-
width: boardSize,
|
|
82
|
-
height: boardSize,
|
|
83
|
-
zIndex: 200,
|
|
84
|
-
}}
|
|
85
|
-
>
|
|
86
|
-
{/* Backdrop -- dims the board and captures cancel taps */}
|
|
87
|
-
<Pressable
|
|
88
|
-
style={{
|
|
89
|
-
position: 'absolute',
|
|
90
|
-
width: boardSize,
|
|
91
|
-
height: boardSize,
|
|
92
|
-
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
93
|
-
}}
|
|
94
|
-
onPress={onCancel}
|
|
95
|
-
/>
|
|
96
|
-
|
|
97
|
-
{/* Piece column */}
|
|
98
|
-
<View
|
|
99
|
-
style={{
|
|
100
|
-
position: 'absolute',
|
|
101
|
-
left: x,
|
|
102
|
-
top: expandDown ? y : y - squareSize * 3,
|
|
103
|
-
width: squareSize,
|
|
104
|
-
backgroundColor: '#ffffff',
|
|
105
|
-
borderRadius: 4,
|
|
106
|
-
// Subtle shadow for elevation
|
|
107
|
-
shadowColor: '#000',
|
|
108
|
-
shadowOffset: { width: 0, height: 2 },
|
|
109
|
-
shadowOpacity: 0.25,
|
|
110
|
-
shadowRadius: 4,
|
|
111
|
-
elevation: 8,
|
|
112
|
-
overflow: 'hidden',
|
|
113
|
-
}}
|
|
114
|
-
>
|
|
115
|
-
{PROMOTION_PIECES.map((piece) => {
|
|
116
|
-
const code = promotionPieceCode(pieceColor, piece);
|
|
117
|
-
return (
|
|
118
|
-
<Pressable
|
|
119
|
-
key={piece}
|
|
120
|
-
onPress={() => handleSelect(piece)}
|
|
121
|
-
style={({ pressed }) => ({
|
|
122
|
-
width: squareSize,
|
|
123
|
-
height: squareSize,
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
justifyContent: 'center',
|
|
126
|
-
backgroundColor: pressed ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
|
|
127
|
-
})}
|
|
128
|
-
accessibilityLabel={`Promote to ${PIECE_NAMES[piece]}`}
|
|
129
|
-
accessibilityRole="button"
|
|
130
|
-
>
|
|
131
|
-
<View style={{ width: pieceSize, height: pieceSize }}>
|
|
132
|
-
{renderPiece(code, pieceSize)}
|
|
133
|
-
</View>
|
|
134
|
-
</Pressable>
|
|
135
|
-
);
|
|
136
|
-
})}
|
|
137
|
-
</View>
|
|
138
|
-
</View>
|
|
139
|
-
);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const PIECE_NAMES: Record<PromotionPiece, string> = {
|
|
143
|
-
q: 'Queen',
|
|
144
|
-
r: 'Rook',
|
|
145
|
-
b: 'Bishop',
|
|
146
|
-
n: 'Knight',
|
|
147
|
-
};
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { View, Pressable } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { ChessColor, PromotionPiece, PieceSetMap } from './types';
|
|
5
|
+
import { squareToXY } from './use-board-pieces';
|
|
6
|
+
import { PROMOTION_PIECE_PADDING } from './constants';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Props
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
type PromotionPickerProps = {
|
|
13
|
+
/** Square the pawn is promoting on */
|
|
14
|
+
square: string;
|
|
15
|
+
/** Color of the promoting pawn */
|
|
16
|
+
pieceColor: 'w' | 'b';
|
|
17
|
+
/** Board size in pixels */
|
|
18
|
+
boardSize: number;
|
|
19
|
+
squareSize: number;
|
|
20
|
+
orientation: ChessColor;
|
|
21
|
+
/** Piece renderer (from renderPiece prop or pieceSet) */
|
|
22
|
+
renderPiece: (code: string, size: number) => React.ReactElement;
|
|
23
|
+
/** Called when user picks a promotion piece */
|
|
24
|
+
onSelect: (piece: PromotionPiece) => void;
|
|
25
|
+
/** Called when user cancels (taps outside) */
|
|
26
|
+
onCancel: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Promotion pieces in order
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const PROMOTION_PIECES: PromotionPiece[] = ['q', 'r', 'b', 'n'];
|
|
34
|
+
|
|
35
|
+
function promotionPieceCode(color: 'w' | 'b', piece: PromotionPiece): string {
|
|
36
|
+
return `${color}${piece}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Component
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Promotion piece picker overlay.
|
|
45
|
+
*
|
|
46
|
+
* Appears as a vertical column of 4 pieces (Q, R, B, N) anchored to the
|
|
47
|
+
* promotion square. Expands downward when promoting on the top edge,
|
|
48
|
+
* upward when promoting on the bottom edge.
|
|
49
|
+
*
|
|
50
|
+
* A semi-transparent backdrop covers the board. Tapping outside cancels.
|
|
51
|
+
*/
|
|
52
|
+
export const PromotionPicker = React.memo(function PromotionPicker({
|
|
53
|
+
square,
|
|
54
|
+
pieceColor,
|
|
55
|
+
boardSize,
|
|
56
|
+
squareSize,
|
|
57
|
+
orientation,
|
|
58
|
+
renderPiece,
|
|
59
|
+
onSelect,
|
|
60
|
+
onCancel,
|
|
61
|
+
}: PromotionPickerProps) {
|
|
62
|
+
const { x, y } = squareToXY(square, squareSize, orientation);
|
|
63
|
+
const pieceSize = squareSize * (1 - PROMOTION_PIECE_PADDING * 2);
|
|
64
|
+
const padding = squareSize * PROMOTION_PIECE_PADDING;
|
|
65
|
+
|
|
66
|
+
// Determine if the picker should expand downward or upward
|
|
67
|
+
// If the promotion square is in the top half, expand downward
|
|
68
|
+
const expandDown = y < boardSize / 2;
|
|
69
|
+
|
|
70
|
+
const handleSelect = useCallback(
|
|
71
|
+
(piece: PromotionPiece) => {
|
|
72
|
+
onSelect(piece);
|
|
73
|
+
},
|
|
74
|
+
[onSelect],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<View
|
|
79
|
+
style={{
|
|
80
|
+
position: 'absolute',
|
|
81
|
+
width: boardSize,
|
|
82
|
+
height: boardSize,
|
|
83
|
+
zIndex: 200,
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
{/* Backdrop -- dims the board and captures cancel taps */}
|
|
87
|
+
<Pressable
|
|
88
|
+
style={{
|
|
89
|
+
position: 'absolute',
|
|
90
|
+
width: boardSize,
|
|
91
|
+
height: boardSize,
|
|
92
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
93
|
+
}}
|
|
94
|
+
onPress={onCancel}
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{/* Piece column */}
|
|
98
|
+
<View
|
|
99
|
+
style={{
|
|
100
|
+
position: 'absolute',
|
|
101
|
+
left: x,
|
|
102
|
+
top: expandDown ? y : y - squareSize * 3,
|
|
103
|
+
width: squareSize,
|
|
104
|
+
backgroundColor: '#ffffff',
|
|
105
|
+
borderRadius: 4,
|
|
106
|
+
// Subtle shadow for elevation
|
|
107
|
+
shadowColor: '#000',
|
|
108
|
+
shadowOffset: { width: 0, height: 2 },
|
|
109
|
+
shadowOpacity: 0.25,
|
|
110
|
+
shadowRadius: 4,
|
|
111
|
+
elevation: 8,
|
|
112
|
+
overflow: 'hidden',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{PROMOTION_PIECES.map((piece) => {
|
|
116
|
+
const code = promotionPieceCode(pieceColor, piece);
|
|
117
|
+
return (
|
|
118
|
+
<Pressable
|
|
119
|
+
key={piece}
|
|
120
|
+
onPress={() => handleSelect(piece)}
|
|
121
|
+
style={({ pressed }) => ({
|
|
122
|
+
width: squareSize,
|
|
123
|
+
height: squareSize,
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
justifyContent: 'center',
|
|
126
|
+
backgroundColor: pressed ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
|
|
127
|
+
})}
|
|
128
|
+
accessibilityLabel={`Promote to ${PIECE_NAMES[piece]}`}
|
|
129
|
+
accessibilityRole="button"
|
|
130
|
+
>
|
|
131
|
+
<View style={{ width: pieceSize, height: pieceSize }}>
|
|
132
|
+
{renderPiece(code, pieceSize)}
|
|
133
|
+
</View>
|
|
134
|
+
</Pressable>
|
|
135
|
+
);
|
|
136
|
+
})}
|
|
137
|
+
</View>
|
|
138
|
+
</View>
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const PIECE_NAMES: Record<PromotionPiece, string> = {
|
|
143
|
+
q: 'Queen',
|
|
144
|
+
r: 'Rook',
|
|
145
|
+
b: 'Bishop',
|
|
146
|
+
n: 'Knight',
|
|
147
|
+
};
|
package/src/static-board.tsx
CHANGED
|
@@ -1,150 +1,187 @@
|
|
|
1
|
-
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
-
import { View, type LayoutChangeEvent } from 'react-native';
|
|
3
|
-
|
|
4
|
-
import type { StaticBoardProps, ChessColor, PieceCode } from './types';
|
|
5
|
-
import { DEFAULT_BOARD_COLORS, DEFAULT_LAST_MOVE_COLOR } from './constants';
|
|
6
|
-
import { DefaultPieceSet } from './pieces';
|
|
7
|
-
import { useBoardPieces, squareToXY } from './use-board-pieces';
|
|
8
|
-
import { BoardBackground } from './board-background';
|
|
9
|
-
import { BoardCoordinates } from './board-coordinates';
|
|
10
|
-
import { BoardHighlights } from './board-highlights';
|
|
11
|
-
import { BoardArrows } from './board-arrows';
|
|
12
|
-
import { BoardAnnotations } from './board-annotations';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Non-interactive chess board — optimized for lists, thumbnails, and analysis previews.
|
|
16
|
-
*
|
|
17
|
-
* No gesture handler, no drag ghost, no legal move dots.
|
|
18
|
-
* Just: background + coordinates + highlights + pieces + arrows + annotations.
|
|
19
|
-
*
|
|
20
|
-
* Significantly lighter than the full Board component.
|
|
21
|
-
*/
|
|
22
|
-
export const StaticBoard = React.memo(function StaticBoard({
|
|
23
|
-
fen,
|
|
24
|
-
orientation = 'white',
|
|
25
|
-
boardSize: boardSizeProp,
|
|
26
|
-
colors,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const boardColors = colors ?? DEFAULT_BOARD_COLORS;
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{/* Layer
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{/* Layer
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
1
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import { View, type LayoutChangeEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import type { StaticBoardProps, ChessColor, PieceCode } from './types';
|
|
5
|
+
import { DEFAULT_BOARD_COLORS, DEFAULT_LAST_MOVE_COLOR, COORDINATE_GUTTER_SCALE } from './constants';
|
|
6
|
+
import { DefaultPieceSet } from './pieces';
|
|
7
|
+
import { useBoardPieces, squareToXY } from './use-board-pieces';
|
|
8
|
+
import { BoardBackground } from './board-background';
|
|
9
|
+
import { BoardCoordinates } from './board-coordinates';
|
|
10
|
+
import { BoardHighlights } from './board-highlights';
|
|
11
|
+
import { BoardArrows } from './board-arrows';
|
|
12
|
+
import { BoardAnnotations } from './board-annotations';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Non-interactive chess board — optimized for lists, thumbnails, and analysis previews.
|
|
16
|
+
*
|
|
17
|
+
* No gesture handler, no drag ghost, no legal move dots.
|
|
18
|
+
* Just: background + coordinates + highlights + pieces + arrows + annotations.
|
|
19
|
+
*
|
|
20
|
+
* Significantly lighter than the full Board component.
|
|
21
|
+
*/
|
|
22
|
+
export const StaticBoard = React.memo(function StaticBoard({
|
|
23
|
+
fen,
|
|
24
|
+
orientation = 'white',
|
|
25
|
+
boardSize: boardSizeProp,
|
|
26
|
+
colors,
|
|
27
|
+
coordinatePosition: coordinatePositionProp,
|
|
28
|
+
withCoordinates,
|
|
29
|
+
renderPiece,
|
|
30
|
+
pieceSet,
|
|
31
|
+
lastMove,
|
|
32
|
+
highlights,
|
|
33
|
+
arrows,
|
|
34
|
+
annotations,
|
|
35
|
+
}: StaticBoardProps) {
|
|
36
|
+
// --- Auto-sizing via onLayout when boardSize not provided ---
|
|
37
|
+
const [measuredSize, setMeasuredSize] = useState(0);
|
|
38
|
+
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
|
39
|
+
const { width, height } = e.nativeEvent.layout;
|
|
40
|
+
setMeasuredSize(Math.min(width, height));
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const outerSize = boardSizeProp ?? measuredSize;
|
|
44
|
+
const boardColors = colors ?? DEFAULT_BOARD_COLORS;
|
|
45
|
+
|
|
46
|
+
// Resolve coordinate position: new prop > legacy boolean > 'none'
|
|
47
|
+
const coordinatePosition = coordinatePositionProp
|
|
48
|
+
?? (withCoordinates ? 'inside' : 'none');
|
|
49
|
+
const isOutside = coordinatePosition === 'outside';
|
|
50
|
+
const isCoordVisible = coordinatePosition !== 'none';
|
|
51
|
+
|
|
52
|
+
const gutterWidth = isOutside ? Math.round((outerSize / 8) * COORDINATE_GUTTER_SCALE) : 0;
|
|
53
|
+
const boardSize = isOutside ? outerSize - gutterWidth : outerSize;
|
|
54
|
+
const squareSize = boardSize / 8;
|
|
55
|
+
|
|
56
|
+
// --- Piece data from FEN ---
|
|
57
|
+
const pieces = useBoardPieces(fen);
|
|
58
|
+
|
|
59
|
+
// --- Resolve piece renderer: renderPiece > pieceSet > DefaultPieceSet ---
|
|
60
|
+
const resolvedRenderer = useMemo(() => {
|
|
61
|
+
if (renderPiece) return renderPiece;
|
|
62
|
+
const set = pieceSet ?? DefaultPieceSet;
|
|
63
|
+
return (code: string, size: number) => {
|
|
64
|
+
const renderer = set[code as PieceCode];
|
|
65
|
+
if (renderer) return renderer(size);
|
|
66
|
+
// Fallback for unknown piece codes
|
|
67
|
+
return <View style={{ width: size, height: size }} />;
|
|
68
|
+
};
|
|
69
|
+
}, [renderPiece, pieceSet]);
|
|
70
|
+
|
|
71
|
+
// If no size yet (auto-sizing), render invisible container for measurement
|
|
72
|
+
if (outerSize === 0) {
|
|
73
|
+
return (
|
|
74
|
+
<View style={{ flex: 1, aspectRatio: 1 }} onLayout={handleLayout} />
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const boardContent = (
|
|
79
|
+
<View
|
|
80
|
+
style={isOutside
|
|
81
|
+
? { width: boardSize, height: boardSize, position: 'absolute', top: 0, right: 0 }
|
|
82
|
+
: { width: boardSize, height: boardSize }}
|
|
83
|
+
onLayout={!isOutside && !boardSizeProp ? handleLayout : undefined}
|
|
84
|
+
accessibilityLabel="Chess board"
|
|
85
|
+
accessibilityRole="image"
|
|
86
|
+
>
|
|
87
|
+
{/* Layer 1: Board background */}
|
|
88
|
+
<BoardBackground
|
|
89
|
+
boardSize={boardSize}
|
|
90
|
+
lightColor={boardColors.light}
|
|
91
|
+
darkColor={boardColors.dark}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
{/* Layer 2: Inside coordinates */}
|
|
95
|
+
{isCoordVisible && !isOutside && (
|
|
96
|
+
<BoardCoordinates
|
|
97
|
+
boardSize={boardSize}
|
|
98
|
+
orientation={orientation}
|
|
99
|
+
lightColor={boardColors.light}
|
|
100
|
+
darkColor={boardColors.dark}
|
|
101
|
+
withLetters
|
|
102
|
+
withNumbers
|
|
103
|
+
position="inside"
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{/* Layer 3: Highlights */}
|
|
108
|
+
<BoardHighlights
|
|
109
|
+
boardSize={boardSize}
|
|
110
|
+
orientation={orientation}
|
|
111
|
+
squareSize={squareSize}
|
|
112
|
+
lastMove={lastMove}
|
|
113
|
+
lastMoveColor={DEFAULT_LAST_MOVE_COLOR}
|
|
114
|
+
checkColor="transparent"
|
|
115
|
+
selectedColor="transparent"
|
|
116
|
+
premoveColor="transparent"
|
|
117
|
+
highlights={highlights}
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
{/* Layer 4: Pieces (static — no animation needed) */}
|
|
121
|
+
<View
|
|
122
|
+
style={{ position: 'absolute', width: boardSize, height: boardSize }}
|
|
123
|
+
pointerEvents="none"
|
|
124
|
+
>
|
|
125
|
+
{pieces.map((piece) => {
|
|
126
|
+
const { x, y } = squareToXY(piece.square, squareSize, orientation);
|
|
127
|
+
return (
|
|
128
|
+
<View
|
|
129
|
+
key={piece.id}
|
|
130
|
+
style={{
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
width: squareSize,
|
|
133
|
+
height: squareSize,
|
|
134
|
+
transform: [{ translateX: x }, { translateY: y }],
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{resolvedRenderer(piece.code, squareSize)}
|
|
138
|
+
</View>
|
|
139
|
+
);
|
|
140
|
+
})}
|
|
141
|
+
</View>
|
|
142
|
+
|
|
143
|
+
{/* Layer 5: Arrows */}
|
|
144
|
+
{arrows && arrows.length > 0 && (
|
|
145
|
+
<BoardArrows
|
|
146
|
+
boardSize={boardSize}
|
|
147
|
+
orientation={orientation}
|
|
148
|
+
arrows={arrows}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{/* Layer 6: Annotations */}
|
|
153
|
+
{annotations && annotations.length > 0 && (
|
|
154
|
+
<BoardAnnotations
|
|
155
|
+
boardSize={boardSize}
|
|
156
|
+
orientation={orientation}
|
|
157
|
+
squareSize={squareSize}
|
|
158
|
+
annotations={annotations}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
</View>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (isOutside) {
|
|
165
|
+
return (
|
|
166
|
+
<View
|
|
167
|
+
style={{ width: outerSize, height: boardSize + gutterWidth }}
|
|
168
|
+
onLayout={boardSizeProp ? undefined : handleLayout}
|
|
169
|
+
>
|
|
170
|
+
{boardContent}
|
|
171
|
+
|
|
172
|
+
<BoardCoordinates
|
|
173
|
+
boardSize={boardSize}
|
|
174
|
+
orientation={orientation}
|
|
175
|
+
lightColor={boardColors.light}
|
|
176
|
+
darkColor={boardColors.dark}
|
|
177
|
+
withLetters
|
|
178
|
+
withNumbers
|
|
179
|
+
position="outside"
|
|
180
|
+
gutterWidth={gutterWidth}
|
|
181
|
+
/>
|
|
182
|
+
</View>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return boardContent;
|
|
187
|
+
});
|