react-native-chess-kit 0.5.2 → 0.5.4

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.
Files changed (42) hide show
  1. package/README.md +19 -0
  2. package/lib/commonjs/board-arrows.js +7 -7
  3. package/lib/commonjs/board-coordinates.js +8 -8
  4. package/lib/commonjs/board.js +48 -39
  5. package/lib/commonjs/board.js.map +1 -1
  6. package/lib/commonjs/promotion-picker.js +9 -9
  7. package/lib/commonjs/promotion-picker.js.map +1 -1
  8. package/lib/commonjs/static-board.js +7 -7
  9. package/lib/commonjs/static-board.js.map +1 -1
  10. package/lib/commonjs/use-board-gesture.js +26 -26
  11. package/lib/commonjs/use-board-gesture.js.map +1 -1
  12. package/lib/commonjs/use-board-state.js +13 -4
  13. package/lib/commonjs/use-board-state.js.map +1 -1
  14. package/lib/module/board-arrows.js +7 -7
  15. package/lib/module/board-coordinates.js +8 -8
  16. package/lib/module/board.js +48 -39
  17. package/lib/module/board.js.map +1 -1
  18. package/lib/module/promotion-picker.js +9 -9
  19. package/lib/module/promotion-picker.js.map +1 -1
  20. package/lib/module/static-board.js +7 -7
  21. package/lib/module/static-board.js.map +1 -1
  22. package/lib/module/use-board-gesture.js +26 -26
  23. package/lib/module/use-board-gesture.js.map +1 -1
  24. package/lib/module/use-board-state.js +13 -4
  25. package/lib/module/use-board-state.js.map +1 -1
  26. package/lib/typescript/board-coordinates.d.ts.map +1 -1
  27. package/lib/typescript/board.d.ts.map +1 -1
  28. package/lib/typescript/promotion-picker.d.ts.map +1 -1
  29. package/lib/typescript/static-board.d.ts.map +1 -1
  30. package/lib/typescript/types.d.ts +14 -3
  31. package/lib/typescript/types.d.ts.map +1 -1
  32. package/lib/typescript/use-board-gesture.d.ts.map +1 -1
  33. package/lib/typescript/use-board-state.d.ts.map +1 -1
  34. package/package.json +95 -75
  35. package/src/board-arrows.tsx +197 -197
  36. package/src/board-coordinates.tsx +192 -192
  37. package/src/board.tsx +59 -56
  38. package/src/promotion-picker.tsx +147 -147
  39. package/src/static-board.tsx +186 -187
  40. package/src/types.ts +27 -6
  41. package/src/use-board-gesture.ts +459 -429
  42. package/src/use-board-state.ts +23 -14
@@ -1,187 +1,186 @@
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
- });
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import { View, type LayoutChangeEvent } from 'react-native';
3
+
4
+ import type { StaticBoardProps, PieceCode } from './types';
5
+ import {
6
+ DEFAULT_BOARD_COLORS,
7
+ DEFAULT_LAST_MOVE_COLOR,
8
+ COORDINATE_GUTTER_SCALE,
9
+ } from './constants';
10
+ import { DefaultPieceSet } from './pieces';
11
+ import { useBoardPieces, squareToXY } from './use-board-pieces';
12
+ import { BoardBackground } from './board-background';
13
+ import { BoardCoordinates } from './board-coordinates';
14
+ import { BoardHighlights } from './board-highlights';
15
+ import { BoardArrows } from './board-arrows';
16
+ import { BoardAnnotations } from './board-annotations';
17
+
18
+ /**
19
+ * Non-interactive chess board — optimized for lists, thumbnails, and analysis previews.
20
+ *
21
+ * No gesture handler, no drag ghost, no legal move dots.
22
+ * Just: background + coordinates + highlights + pieces + arrows + annotations.
23
+ *
24
+ * Significantly lighter than the full Board component.
25
+ */
26
+ export const StaticBoard = React.memo(function StaticBoard({
27
+ fen,
28
+ orientation = 'white',
29
+ boardSize: boardSizeProp,
30
+ colors,
31
+ coordinatePosition: coordinatePositionProp,
32
+ withCoordinates,
33
+ renderPiece,
34
+ pieceSet,
35
+ lastMove,
36
+ highlights,
37
+ arrows,
38
+ annotations,
39
+ }: StaticBoardProps) {
40
+ // --- Auto-sizing via onLayout when boardSize not provided ---
41
+ const [measuredSize, setMeasuredSize] = useState(0);
42
+ const handleLayout = useCallback((e: LayoutChangeEvent) => {
43
+ const { width, height } = e.nativeEvent.layout;
44
+ setMeasuredSize(Math.min(width, height));
45
+ }, []);
46
+
47
+ const outerSize = boardSizeProp ?? measuredSize;
48
+ const boardColors = colors ?? DEFAULT_BOARD_COLORS;
49
+
50
+ // Resolve coordinate position: new prop > legacy boolean > 'none'
51
+ const coordinatePosition = coordinatePositionProp ?? (withCoordinates ? 'inside' : 'none');
52
+ const isOutside = coordinatePosition === 'outside';
53
+ const isCoordVisible = coordinatePosition !== 'none';
54
+
55
+ const gutterWidth = isOutside ? Math.round((outerSize / 8) * COORDINATE_GUTTER_SCALE) : 0;
56
+ const boardSize = isOutside ? outerSize - gutterWidth : outerSize;
57
+ const squareSize = boardSize / 8;
58
+
59
+ // --- Piece data from FEN ---
60
+ const pieces = useBoardPieces(fen);
61
+
62
+ // --- Resolve piece renderer: renderPiece > pieceSet > DefaultPieceSet ---
63
+ const resolvedRenderer = useMemo(() => {
64
+ if (renderPiece) return renderPiece;
65
+ const set = pieceSet ?? DefaultPieceSet;
66
+ return (code: string, size: number) => {
67
+ const renderer = set[code as PieceCode];
68
+ if (renderer) return renderer(size);
69
+ // Fallback for unknown piece codes
70
+ return <View style={{ width: size, height: size }} />;
71
+ };
72
+ }, [renderPiece, pieceSet]);
73
+
74
+ // If no size yet (auto-sizing), render invisible container for measurement
75
+ if (outerSize === 0) {
76
+ return <View style={{ flex: 1, aspectRatio: 1 }} onLayout={handleLayout} />;
77
+ }
78
+
79
+ const boardContent = (
80
+ <View
81
+ style={
82
+ isOutside
83
+ ? { width: boardSize, height: boardSize, position: 'absolute', top: 0, right: 0 }
84
+ : { width: boardSize, height: boardSize }
85
+ }
86
+ onLayout={!isOutside && !boardSizeProp ? handleLayout : undefined}
87
+ accessibilityLabel="Chess board"
88
+ accessibilityRole="image"
89
+ >
90
+ {/* Layer 1: Board background */}
91
+ <BoardBackground
92
+ boardSize={boardSize}
93
+ lightColor={boardColors.light}
94
+ darkColor={boardColors.dark}
95
+ />
96
+
97
+ {/* Layer 2: Inside coordinates */}
98
+ {isCoordVisible && !isOutside && (
99
+ <BoardCoordinates
100
+ boardSize={boardSize}
101
+ orientation={orientation}
102
+ lightColor={boardColors.light}
103
+ darkColor={boardColors.dark}
104
+ withLetters
105
+ withNumbers
106
+ position="inside"
107
+ />
108
+ )}
109
+
110
+ {/* Layer 3: Highlights */}
111
+ <BoardHighlights
112
+ boardSize={boardSize}
113
+ orientation={orientation}
114
+ squareSize={squareSize}
115
+ lastMove={lastMove}
116
+ lastMoveColor={DEFAULT_LAST_MOVE_COLOR}
117
+ checkColor="transparent"
118
+ selectedColor="transparent"
119
+ premoveColor="transparent"
120
+ highlights={highlights}
121
+ />
122
+
123
+ {/* Layer 4: Pieces (static — no animation needed) */}
124
+ <View
125
+ style={{ position: 'absolute', width: boardSize, height: boardSize }}
126
+ pointerEvents="none"
127
+ >
128
+ {pieces.map((piece) => {
129
+ const { x, y } = squareToXY(piece.square, squareSize, orientation);
130
+ return (
131
+ <View
132
+ key={piece.id}
133
+ style={{
134
+ position: 'absolute',
135
+ width: squareSize,
136
+ height: squareSize,
137
+ transform: [{ translateX: x }, { translateY: y }],
138
+ }}
139
+ >
140
+ {resolvedRenderer(piece.code, squareSize)}
141
+ </View>
142
+ );
143
+ })}
144
+ </View>
145
+
146
+ {/* Layer 5: Arrows */}
147
+ {arrows && arrows.length > 0 && (
148
+ <BoardArrows boardSize={boardSize} orientation={orientation} arrows={arrows} />
149
+ )}
150
+
151
+ {/* Layer 6: Annotations */}
152
+ {annotations && annotations.length > 0 && (
153
+ <BoardAnnotations
154
+ boardSize={boardSize}
155
+ orientation={orientation}
156
+ squareSize={squareSize}
157
+ annotations={annotations}
158
+ />
159
+ )}
160
+ </View>
161
+ );
162
+
163
+ if (isOutside) {
164
+ return (
165
+ <View
166
+ style={{ width: outerSize, height: boardSize + gutterWidth }}
167
+ onLayout={boardSizeProp ? undefined : handleLayout}
168
+ >
169
+ {boardContent}
170
+
171
+ <BoardCoordinates
172
+ boardSize={boardSize}
173
+ orientation={orientation}
174
+ lightColor={boardColors.light}
175
+ darkColor={boardColors.dark}
176
+ withLetters
177
+ withNumbers
178
+ position="outside"
179
+ gutterWidth={gutterWidth}
180
+ />
181
+ </View>
182
+ );
183
+ }
184
+
185
+ return boardContent;
186
+ });
package/src/types.ts CHANGED
@@ -25,8 +25,18 @@ export type HapticType = 'select' | 'move' | 'capture' | 'error';
25
25
 
26
26
  /** Standard piece codes used throughout the library */
27
27
  export type PieceCode =
28
- | 'wp' | 'wn' | 'wb' | 'wr' | 'wq' | 'wk'
29
- | 'bp' | 'bn' | 'bb' | 'br' | 'bq' | 'bk';
28
+ | 'wp'
29
+ | 'wn'
30
+ | 'wb'
31
+ | 'wr'
32
+ | 'wq'
33
+ | 'wk'
34
+ | 'bp'
35
+ | 'bn'
36
+ | 'bb'
37
+ | 'br'
38
+ | 'bq'
39
+ | 'bk';
30
40
 
31
41
  /** A single piece on the board with its position and identity */
32
42
  export type BoardPiece = {
@@ -278,15 +288,26 @@ export type BoardProps = {
278
288
  // --- Promotion ---
279
289
 
280
290
  /**
281
- * Called when a pawn promotion occurs. If not provided, auto-promotes to queen.
282
- * Return the chosen piece to complete the promotion.
291
+ * Auto-promote pawns to this piece without showing the picker.
292
+ * When set, the promotion picker is skipped entirely.
293
+ * Example: `autoPromoteTo="q"` always promotes to queen.
294
+ */
295
+ autoPromoteTo?: PromotionPiece;
296
+
297
+ /**
298
+ * Called when a pawn promotion occurs. If not provided, the built-in
299
+ * promotion picker is shown. Return the chosen piece to complete the
300
+ * promotion, or throw/reject to cancel.
301
+ *
302
+ * When provided, the built-in picker is NOT shown — the consumer is
303
+ * expected to handle piece selection via their own UI.
283
304
  */
284
305
  onPromotion?: (from: string, to: string) => Promise<PromotionPiece> | PromotionPiece;
285
306
 
286
307
  // --- Callbacks ---
287
308
 
288
- /** Called after a visual move is applied */
289
- onMove?: (info: { from: string; to: string }) => void;
309
+ /** Called after a visual move is applied. Includes `promotion` when a pawn promotes. */
310
+ onMove?: (info: { from: string; to: string; promotion?: PromotionPiece }) => void;
290
311
  /** Called when a piece is tapped */
291
312
  onPieceClick?: (square: string, piece: PieceCode) => void;
292
313
  /** Called when an empty square or opponent piece is tapped */