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.
Files changed (82) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/lib/commonjs/board-annotations.js +8 -8
  4. package/lib/commonjs/board-arrows.js +7 -7
  5. package/lib/commonjs/board-background.js +5 -5
  6. package/lib/commonjs/board-coordinates.js +78 -11
  7. package/lib/commonjs/board-coordinates.js.map +1 -1
  8. package/lib/commonjs/board-drag-ghost.js +10 -10
  9. package/lib/commonjs/board-highlights.js +15 -15
  10. package/lib/commonjs/board-legal-dots.js +5 -5
  11. package/lib/commonjs/board-piece.js +25 -25
  12. package/lib/commonjs/board-pieces.js +6 -6
  13. package/lib/commonjs/board.js +76 -35
  14. package/lib/commonjs/board.js.map +1 -1
  15. package/lib/commonjs/constants.js +4 -1
  16. package/lib/commonjs/constants.js.map +1 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/promotion-picker.js +8 -8
  19. package/lib/commonjs/static-board.js +51 -16
  20. package/lib/commonjs/static-board.js.map +1 -1
  21. package/lib/commonjs/use-board-gesture.js +52 -33
  22. package/lib/commonjs/use-board-gesture.js.map +1 -1
  23. package/lib/commonjs/use-board-pieces.js +15 -15
  24. package/lib/commonjs/use-board-state.js +8 -8
  25. package/lib/commonjs/use-premove.js +12 -12
  26. package/lib/module/board-annotations.js +8 -8
  27. package/lib/module/board-arrows.js +7 -7
  28. package/lib/module/board-background.js +5 -5
  29. package/lib/module/board-coordinates.js +79 -12
  30. package/lib/module/board-coordinates.js.map +1 -1
  31. package/lib/module/board-drag-ghost.js +10 -10
  32. package/lib/module/board-highlights.js +15 -15
  33. package/lib/module/board-legal-dots.js +5 -5
  34. package/lib/module/board-piece.js +25 -25
  35. package/lib/module/board-pieces.js +6 -6
  36. package/lib/module/board.js +77 -36
  37. package/lib/module/board.js.map +1 -1
  38. package/lib/module/constants.js +3 -0
  39. package/lib/module/constants.js.map +1 -1
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/promotion-picker.js +8 -8
  42. package/lib/module/static-board.js +52 -17
  43. package/lib/module/static-board.js.map +1 -1
  44. package/lib/module/use-board-gesture.js +52 -33
  45. package/lib/module/use-board-gesture.js.map +1 -1
  46. package/lib/module/use-board-pieces.js +15 -15
  47. package/lib/module/use-board-state.js +8 -8
  48. package/lib/module/use-premove.js +12 -12
  49. package/lib/typescript/board-coordinates.d.ts +10 -4
  50. package/lib/typescript/board-coordinates.d.ts.map +1 -1
  51. package/lib/typescript/board.d.ts.map +1 -1
  52. package/lib/typescript/constants.d.ts +2 -0
  53. package/lib/typescript/constants.d.ts.map +1 -1
  54. package/lib/typescript/index.d.ts +1 -1
  55. package/lib/typescript/index.d.ts.map +1 -1
  56. package/lib/typescript/static-board.d.ts.map +1 -1
  57. package/lib/typescript/types.d.ts +23 -3
  58. package/lib/typescript/types.d.ts.map +1 -1
  59. package/lib/typescript/use-board-gesture.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/board-annotations.tsx +147 -147
  62. package/src/board-arrows.tsx +197 -197
  63. package/src/board-background.tsx +46 -46
  64. package/src/board-coordinates.tsx +192 -98
  65. package/src/board-drag-ghost.tsx +132 -132
  66. package/src/board-highlights.tsx +226 -226
  67. package/src/board-legal-dots.tsx +73 -73
  68. package/src/board-piece.tsx +160 -160
  69. package/src/board-pieces.tsx +63 -63
  70. package/src/board.tsx +685 -641
  71. package/src/constants.ts +103 -100
  72. package/src/index.ts +101 -100
  73. package/src/pieces/default-pieces.tsx +383 -383
  74. package/src/pieces/index.ts +1 -1
  75. package/src/promotion-picker.tsx +147 -147
  76. package/src/static-board.tsx +187 -150
  77. package/src/themes.ts +129 -129
  78. package/src/types.ts +373 -352
  79. package/src/use-board-gesture.ts +429 -412
  80. package/src/use-board-pieces.ts +158 -158
  81. package/src/use-board-state.ts +111 -111
  82. package/src/use-premove.ts +59 -59
@@ -1,98 +1,192 @@
1
- import React from 'react';
2
- import { View, Text } from 'react-native';
3
-
4
- import type { ChessColor } from './types';
5
-
6
- type BoardCoordinatesProps = {
7
- boardSize: number;
8
- orientation: ChessColor;
9
- lightColor: string;
10
- darkColor: string;
11
- withLetters: boolean;
12
- withNumbers: boolean;
13
- };
14
-
15
- const FILES_WHITE = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
16
- const FILES_BLACK = ['h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'];
17
- const RANKS_WHITE = ['8', '7', '6', '5', '4', '3', '2', '1'];
18
- const RANKS_BLACK = ['1', '2', '3', '4', '5', '6', '7', '8'];
19
-
20
- /**
21
- * File letters (a-h) and rank numbers (1-8) drawn on the board edges.
22
- *
23
- * Rendered as absolute-positioned Text components inside each corner square.
24
- * File letters appear on the bottom rank, rank numbers on the left file.
25
- * Colors alternate to contrast with the square behind them.
26
- */
27
- export const BoardCoordinates = React.memo(function BoardCoordinates({
28
- boardSize,
29
- orientation,
30
- lightColor,
31
- darkColor,
32
- withLetters,
33
- withNumbers,
34
- }: BoardCoordinatesProps) {
35
- if (!withLetters && !withNumbers) return null;
36
-
37
- const squareSize = boardSize / 8;
38
- const fontSize = squareSize * 0.22;
39
- const padding = squareSize * 0.06;
40
- const files = orientation === 'white' ? FILES_WHITE : FILES_BLACK;
41
- const ranks = orientation === 'white' ? RANKS_WHITE : RANKS_BLACK;
42
-
43
- return (
44
- <View
45
- style={{ position: 'absolute', width: boardSize, height: boardSize }}
46
- pointerEvents="none"
47
- >
48
- {/* Rank numbers along left edge (inside each row's first square) */}
49
- {withNumbers &&
50
- ranks.map((rank, row) => {
51
- // First column square: row 0 col 0 = light if (0+0)%2===0
52
- const isLight = row % 2 === 0;
53
- const textColor = isLight ? darkColor : lightColor;
54
-
55
- return (
56
- <Text
57
- key={`r-${rank}`}
58
- style={{
59
- position: 'absolute',
60
- left: padding,
61
- top: row * squareSize + padding,
62
- fontSize,
63
- fontWeight: '700',
64
- color: textColor,
65
- }}
66
- >
67
- {rank}
68
- </Text>
69
- );
70
- })}
71
-
72
- {/* File letters along bottom edge (inside each column's last square) */}
73
- {withLetters &&
74
- files.map((file, col) => {
75
- // Last row (7), column col: light if (7+col)%2===0
76
- const isLight = (7 + col) % 2 === 0;
77
- const textColor = isLight ? darkColor : lightColor;
78
-
79
- return (
80
- <Text
81
- key={`f-${file}`}
82
- style={{
83
- position: 'absolute',
84
- right: (7 - col) * squareSize + padding,
85
- bottom: padding,
86
- fontSize,
87
- fontWeight: '700',
88
- color: textColor,
89
- textAlign: 'right',
90
- }}
91
- >
92
- {file}
93
- </Text>
94
- );
95
- })}
96
- </View>
97
- );
98
- });
1
+ import React from 'react';
2
+ import { View, Text } from 'react-native';
3
+
4
+ import type { ChessColor, CoordinatePosition } from './types';
5
+
6
+ type BoardCoordinatesProps = {
7
+ boardSize: number;
8
+ orientation: ChessColor;
9
+ lightColor: string;
10
+ darkColor: string;
11
+ withLetters: boolean;
12
+ withNumbers: boolean;
13
+ /** 'inside' overlays on edge squares, 'outside' renders in a gutter area. */
14
+ position?: 'inside' | 'outside';
15
+ /** Gutter width in pixels (only used when position='outside'). */
16
+ gutterWidth?: number;
17
+ };
18
+
19
+ const FILES_WHITE = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
20
+ const FILES_BLACK = ['h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'];
21
+ const RANKS_WHITE = ['8', '7', '6', '5', '4', '3', '2', '1'];
22
+ const RANKS_BLACK = ['1', '2', '3', '4', '5', '6', '7', '8'];
23
+
24
+ /**
25
+ * File letters (a-h) and rank numbers (1-8) drawn on or around the board.
26
+ *
27
+ * Two modes:
28
+ * - **inside** (default): absolute-positioned inside each edge square, colors
29
+ * alternate to contrast with the square behind them.
30
+ * - **outside**: rendered in a gutter area around the board. Rank numbers to
31
+ * the left, file letters along the bottom. Uses the dark square color.
32
+ */
33
+ export const BoardCoordinates = React.memo(function BoardCoordinates({
34
+ boardSize,
35
+ orientation,
36
+ lightColor,
37
+ darkColor,
38
+ withLetters,
39
+ withNumbers,
40
+ position = 'inside',
41
+ gutterWidth = 0,
42
+ }: BoardCoordinatesProps) {
43
+ if (!withLetters && !withNumbers) return null;
44
+
45
+ const squareSize = boardSize / 8;
46
+ const files = orientation === 'white' ? FILES_WHITE : FILES_BLACK;
47
+ const ranks = orientation === 'white' ? RANKS_WHITE : RANKS_BLACK;
48
+
49
+ // ── Outside mode: labels in gutter area around the board ──
50
+ if (position === 'outside') {
51
+ const fontSize = gutterWidth * 0.65;
52
+ const textColor = darkColor;
53
+
54
+ return (
55
+ <>
56
+ {/* Rank numbers — left gutter, vertically centered on each row */}
57
+ {withNumbers && (
58
+ <View
59
+ style={{
60
+ position: 'absolute',
61
+ left: 0,
62
+ top: 0,
63
+ width: gutterWidth,
64
+ height: boardSize,
65
+ }}
66
+ pointerEvents="none"
67
+ >
68
+ {ranks.map((rank, row) => (
69
+ <View
70
+ key={`r-${rank}`}
71
+ style={{
72
+ position: 'absolute',
73
+ top: row * squareSize,
74
+ width: gutterWidth,
75
+ height: squareSize,
76
+ alignItems: 'center',
77
+ justifyContent: 'center',
78
+ }}
79
+ >
80
+ <Text
81
+ style={{
82
+ fontSize,
83
+ fontWeight: '600',
84
+ color: textColor,
85
+ }}
86
+ >
87
+ {rank}
88
+ </Text>
89
+ </View>
90
+ ))}
91
+ </View>
92
+ )}
93
+
94
+ {/* File letters — bottom gutter, horizontally centered on each column */}
95
+ {withLetters && (
96
+ <View
97
+ style={{
98
+ position: 'absolute',
99
+ left: withNumbers ? gutterWidth : 0,
100
+ bottom: 0,
101
+ width: boardSize,
102
+ height: gutterWidth,
103
+ }}
104
+ pointerEvents="none"
105
+ >
106
+ {files.map((file, col) => (
107
+ <View
108
+ key={`f-${file}`}
109
+ style={{
110
+ position: 'absolute',
111
+ left: col * squareSize,
112
+ width: squareSize,
113
+ height: gutterWidth,
114
+ alignItems: 'center',
115
+ justifyContent: 'center',
116
+ }}
117
+ >
118
+ <Text
119
+ style={{
120
+ fontSize,
121
+ fontWeight: '600',
122
+ color: textColor,
123
+ }}
124
+ >
125
+ {file}
126
+ </Text>
127
+ </View>
128
+ ))}
129
+ </View>
130
+ )}
131
+ </>
132
+ );
133
+ }
134
+
135
+ // ── Inside mode (default): absolute-positioned inside edge squares ──
136
+ const fontSize = squareSize * 0.22;
137
+ const padding = squareSize * 0.06;
138
+
139
+ return (
140
+ <View
141
+ style={{ position: 'absolute', width: boardSize, height: boardSize }}
142
+ pointerEvents="none"
143
+ >
144
+ {/* Rank numbers along left edge (inside each row's first square) */}
145
+ {withNumbers &&
146
+ ranks.map((rank, row) => {
147
+ const isLight = row % 2 === 0;
148
+ const textColor = isLight ? darkColor : lightColor;
149
+
150
+ return (
151
+ <Text
152
+ key={`r-${rank}`}
153
+ style={{
154
+ position: 'absolute',
155
+ left: padding,
156
+ top: row * squareSize + padding,
157
+ fontSize,
158
+ fontWeight: '700',
159
+ color: textColor,
160
+ }}
161
+ >
162
+ {rank}
163
+ </Text>
164
+ );
165
+ })}
166
+
167
+ {/* File letters along bottom edge (inside each column's last square) */}
168
+ {withLetters &&
169
+ files.map((file, col) => {
170
+ const isLight = (7 + col) % 2 === 0;
171
+ const textColor = isLight ? darkColor : lightColor;
172
+
173
+ return (
174
+ <Text
175
+ key={`f-${file}`}
176
+ style={{
177
+ position: 'absolute',
178
+ right: (7 - col) * squareSize + padding,
179
+ bottom: padding,
180
+ fontSize,
181
+ fontWeight: '700',
182
+ color: textColor,
183
+ textAlign: 'right',
184
+ }}
185
+ >
186
+ {file}
187
+ </Text>
188
+ );
189
+ })}
190
+ </View>
191
+ );
192
+ });
@@ -1,132 +1,132 @@
1
- import React from 'react';
2
- import Animated, {
3
- useAnimatedStyle,
4
- type SharedValue,
5
- } from 'react-native-reanimated';
6
-
7
- type BoardDragGhostProps = {
8
- squareSize: number;
9
- isDragging: SharedValue<boolean>;
10
- dragX: SharedValue<number>;
11
- dragY: SharedValue<number>;
12
- dragPieceCode: SharedValue<string | null>;
13
- /** Render the piece image for a given piece code */
14
- renderPiece: (code: string, size: number) => React.ReactElement;
15
- };
16
-
17
- /**
18
- * Floating piece that follows the user's finger during drag.
19
- *
20
- * Only ONE instance exists — not one per piece. It reads drag position
21
- * from shared values on the UI thread, so zero JS bridge calls and
22
- * zero re-renders while dragging.
23
- */
24
- export const BoardDragGhost = React.memo(function BoardDragGhost({
25
- squareSize,
26
- isDragging,
27
- dragX,
28
- dragY,
29
- dragPieceCode,
30
- renderPiece,
31
- }: BoardDragGhostProps) {
32
- const animatedStyle = useAnimatedStyle(() => {
33
- if (!isDragging.value || !dragPieceCode.value) {
34
- return { opacity: 0, transform: [{ translateX: 0 }, { translateY: 0 }] };
35
- }
36
-
37
- return {
38
- opacity: 1,
39
- // Center the piece on the finger, slightly above for visibility
40
- transform: [
41
- { translateX: dragX.value - squareSize / 2 },
42
- { translateY: dragY.value - squareSize },
43
- { scale: 1.1 },
44
- ],
45
- };
46
- });
47
-
48
- return (
49
- <Animated.View
50
- style={[
51
- {
52
- position: 'absolute',
53
- width: squareSize,
54
- height: squareSize,
55
- zIndex: 100,
56
- },
57
- animatedStyle,
58
- ]}
59
- pointerEvents="none"
60
- >
61
- <DragGhostContent
62
- renderPiece={renderPiece}
63
- squareSize={squareSize}
64
- dragPieceCode={dragPieceCode}
65
- />
66
- </Animated.View>
67
- );
68
- });
69
-
70
- /**
71
- * Inner content that renders the actual piece image.
72
- * Separate component so the Animated.View wrapper doesn't need
73
- * to re-render when the piece code changes — it uses shared value.
74
- */
75
- const DragGhostContent = React.memo(function DragGhostContent({
76
- renderPiece,
77
- squareSize,
78
- dragPieceCode,
79
- }: {
80
- renderPiece: (code: string, size: number) => React.ReactElement;
81
- squareSize: number;
82
- dragPieceCode: SharedValue<string | null>;
83
- }) {
84
- // We render all 12 possible piece types and show/hide based on dragPieceCode.
85
- // This avoids re-mounting the Image component during drag.
86
- // Only the opacity changes — pure worklet animation.
87
- const codes = PIECE_CODES;
88
-
89
- return (
90
- <>
91
- {codes.map((code) => (
92
- <GhostPieceSlot
93
- key={code}
94
- code={code}
95
- squareSize={squareSize}
96
- dragPieceCode={dragPieceCode}
97
- renderPiece={renderPiece}
98
- />
99
- ))}
100
- </>
101
- );
102
- });
103
-
104
- const GhostPieceSlot = React.memo(function GhostPieceSlot({
105
- code,
106
- squareSize,
107
- dragPieceCode,
108
- renderPiece,
109
- }: {
110
- code: string;
111
- squareSize: number;
112
- dragPieceCode: SharedValue<string | null>;
113
- renderPiece: (code: string, size: number) => React.ReactElement;
114
- }) {
115
- const animatedStyle = useAnimatedStyle(() => ({
116
- opacity: dragPieceCode.value === code ? 1 : 0,
117
- }));
118
-
119
- return (
120
- <Animated.View
121
- style={[
122
- { position: 'absolute', width: squareSize, height: squareSize },
123
- animatedStyle,
124
- ]}
125
- >
126
- {renderPiece(code, squareSize)}
127
- </Animated.View>
128
- );
129
- });
130
-
131
- // All 12 piece codes — pre-computed to avoid allocation
132
- const PIECE_CODES = ['wp', 'wn', 'wb', 'wr', 'wq', 'wk', 'bp', 'bn', 'bb', 'br', 'bq', 'bk'];
1
+ import React from 'react';
2
+ import Animated, {
3
+ useAnimatedStyle,
4
+ type SharedValue,
5
+ } from 'react-native-reanimated';
6
+
7
+ type BoardDragGhostProps = {
8
+ squareSize: number;
9
+ isDragging: SharedValue<boolean>;
10
+ dragX: SharedValue<number>;
11
+ dragY: SharedValue<number>;
12
+ dragPieceCode: SharedValue<string | null>;
13
+ /** Render the piece image for a given piece code */
14
+ renderPiece: (code: string, size: number) => React.ReactElement;
15
+ };
16
+
17
+ /**
18
+ * Floating piece that follows the user's finger during drag.
19
+ *
20
+ * Only ONE instance exists — not one per piece. It reads drag position
21
+ * from shared values on the UI thread, so zero JS bridge calls and
22
+ * zero re-renders while dragging.
23
+ */
24
+ export const BoardDragGhost = React.memo(function BoardDragGhost({
25
+ squareSize,
26
+ isDragging,
27
+ dragX,
28
+ dragY,
29
+ dragPieceCode,
30
+ renderPiece,
31
+ }: BoardDragGhostProps) {
32
+ const animatedStyle = useAnimatedStyle(() => {
33
+ if (!isDragging.value || !dragPieceCode.value) {
34
+ return { opacity: 0, transform: [{ translateX: 0 }, { translateY: 0 }] };
35
+ }
36
+
37
+ return {
38
+ opacity: 1,
39
+ // Center the piece on the finger, slightly above for visibility
40
+ transform: [
41
+ { translateX: dragX.value - squareSize / 2 },
42
+ { translateY: dragY.value - squareSize },
43
+ { scale: 1.1 },
44
+ ],
45
+ };
46
+ });
47
+
48
+ return (
49
+ <Animated.View
50
+ style={[
51
+ {
52
+ position: 'absolute',
53
+ width: squareSize,
54
+ height: squareSize,
55
+ zIndex: 100,
56
+ },
57
+ animatedStyle,
58
+ ]}
59
+ pointerEvents="none"
60
+ >
61
+ <DragGhostContent
62
+ renderPiece={renderPiece}
63
+ squareSize={squareSize}
64
+ dragPieceCode={dragPieceCode}
65
+ />
66
+ </Animated.View>
67
+ );
68
+ });
69
+
70
+ /**
71
+ * Inner content that renders the actual piece image.
72
+ * Separate component so the Animated.View wrapper doesn't need
73
+ * to re-render when the piece code changes — it uses shared value.
74
+ */
75
+ const DragGhostContent = React.memo(function DragGhostContent({
76
+ renderPiece,
77
+ squareSize,
78
+ dragPieceCode,
79
+ }: {
80
+ renderPiece: (code: string, size: number) => React.ReactElement;
81
+ squareSize: number;
82
+ dragPieceCode: SharedValue<string | null>;
83
+ }) {
84
+ // We render all 12 possible piece types and show/hide based on dragPieceCode.
85
+ // This avoids re-mounting the Image component during drag.
86
+ // Only the opacity changes — pure worklet animation.
87
+ const codes = PIECE_CODES;
88
+
89
+ return (
90
+ <>
91
+ {codes.map((code) => (
92
+ <GhostPieceSlot
93
+ key={code}
94
+ code={code}
95
+ squareSize={squareSize}
96
+ dragPieceCode={dragPieceCode}
97
+ renderPiece={renderPiece}
98
+ />
99
+ ))}
100
+ </>
101
+ );
102
+ });
103
+
104
+ const GhostPieceSlot = React.memo(function GhostPieceSlot({
105
+ code,
106
+ squareSize,
107
+ dragPieceCode,
108
+ renderPiece,
109
+ }: {
110
+ code: string;
111
+ squareSize: number;
112
+ dragPieceCode: SharedValue<string | null>;
113
+ renderPiece: (code: string, size: number) => React.ReactElement;
114
+ }) {
115
+ const animatedStyle = useAnimatedStyle(() => ({
116
+ opacity: dragPieceCode.value === code ? 1 : 0,
117
+ }));
118
+
119
+ return (
120
+ <Animated.View
121
+ style={[
122
+ { position: 'absolute', width: squareSize, height: squareSize },
123
+ animatedStyle,
124
+ ]}
125
+ >
126
+ {renderPiece(code, squareSize)}
127
+ </Animated.View>
128
+ );
129
+ });
130
+
131
+ // All 12 piece codes — pre-computed to avoid allocation
132
+ const PIECE_CODES = ['wp', 'wn', 'wb', 'wr', 'wq', 'wk', 'bp', 'bn', 'bb', 'br', 'bq', 'bk'];