@react-chess-tools/react-chess-game 0.5.1 → 1.0.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.cjs +775 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +264 -0
  5. package/dist/index.d.ts +264 -0
  6. package/dist/{index.mjs → index.js} +172 -38
  7. package/dist/index.js.map +1 -0
  8. package/package.json +18 -9
  9. package/src/components/ChessGame/Theme.stories.tsx +242 -0
  10. package/src/components/ChessGame/ThemePresets.stories.tsx +144 -0
  11. package/src/components/ChessGame/parts/Board.tsx +6 -4
  12. package/src/components/ChessGame/parts/Root.tsx +11 -1
  13. package/src/docs/Theming.mdx +281 -0
  14. package/src/hooks/useChessGame.ts +23 -7
  15. package/src/index.ts +20 -0
  16. package/src/theme/__tests__/context.test.tsx +75 -0
  17. package/src/theme/__tests__/defaults.test.ts +61 -0
  18. package/src/theme/__tests__/utils.test.ts +106 -0
  19. package/src/theme/context.tsx +37 -0
  20. package/src/theme/defaults.ts +22 -0
  21. package/src/theme/index.ts +36 -0
  22. package/src/theme/presets.ts +41 -0
  23. package/src/theme/types.ts +56 -0
  24. package/src/theme/utils.ts +47 -0
  25. package/src/utils/__tests__/board.test.ts +118 -0
  26. package/src/utils/board.ts +18 -9
  27. package/src/utils/chess.ts +25 -5
  28. package/coverage/clover.xml +0 -6
  29. package/coverage/coverage-final.json +0 -1
  30. package/coverage/lcov-report/base.css +0 -224
  31. package/coverage/lcov-report/block-navigation.js +0 -87
  32. package/coverage/lcov-report/favicon.png +0 -0
  33. package/coverage/lcov-report/index.html +0 -101
  34. package/coverage/lcov-report/prettify.css +0 -1
  35. package/coverage/lcov-report/prettify.js +0 -2
  36. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  37. package/coverage/lcov-report/sorter.js +0 -196
  38. package/coverage/lcov.info +0 -0
  39. package/dist/index.d.mts +0 -143
  40. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,264 @@
1
+ import { ChessboardOptions } from 'react-chessboard';
2
+ import * as React$1 from 'react';
3
+ import React__default, { CSSProperties } from 'react';
4
+ import * as chess_js from 'chess.js';
5
+ import { Color, Chess } from 'chess.js';
6
+
7
+ type Sound = "check" | "move" | "capture" | "gameOver";
8
+ type Sounds = Record<Sound, HTMLAudioElement>;
9
+
10
+ type SoundsProps = {
11
+ sounds?: Partial<Record<Sound, string>>;
12
+ };
13
+
14
+ interface ChessGameProps {
15
+ options?: ChessboardOptions;
16
+ }
17
+
18
+ /**
19
+ * Board appearance configuration - colors for light and dark squares
20
+ */
21
+ interface BoardTheme {
22
+ /** Style for light squares */
23
+ lightSquare: CSSProperties;
24
+ /** Style for dark squares */
25
+ darkSquare: CSSProperties;
26
+ }
27
+ /**
28
+ * Game state highlight colors (RGBA color strings)
29
+ */
30
+ interface StateTheme {
31
+ /** Background color for last move from/to squares */
32
+ lastMove: string;
33
+ /** Background color for king when in check */
34
+ check: string;
35
+ /** Background color for currently selected piece's square */
36
+ activeSquare: string;
37
+ /** Full CSSProperties for valid drop target squares */
38
+ dropSquare: CSSProperties;
39
+ }
40
+ /**
41
+ * Move indicator styling - colors for move dots and capture rings
42
+ */
43
+ interface IndicatorTheme {
44
+ /** Color for move dots on empty destination squares (used in radial-gradient) */
45
+ move: string;
46
+ /** Color for capture rings on capturable pieces (used in radial-gradient) */
47
+ capture: string;
48
+ }
49
+ /**
50
+ * Complete theme configuration for ChessGame component
51
+ */
52
+ interface ChessGameTheme {
53
+ board: BoardTheme;
54
+ state: StateTheme;
55
+ indicators: IndicatorTheme;
56
+ }
57
+ /**
58
+ * Utility type for creating partial versions of nested objects
59
+ */
60
+ type DeepPartial<T> = {
61
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
62
+ };
63
+ /**
64
+ * Partial theme for user customization - allows overriding only specific properties
65
+ */
66
+ type PartialChessGameTheme = DeepPartial<ChessGameTheme>;
67
+
68
+ interface RootProps {
69
+ fen?: string;
70
+ orientation?: Color;
71
+ /** Optional theme configuration. Supports partial themes - only override the colors you need. */
72
+ theme?: PartialChessGameTheme;
73
+ }
74
+
75
+ type useChessGameProps = {
76
+ fen?: string;
77
+ orientation?: Color;
78
+ };
79
+ declare const useChessGame: ({ fen, orientation: initialOrientation, }?: useChessGameProps) => {
80
+ game: Chess;
81
+ currentFen: string;
82
+ currentPosition: string;
83
+ orientation: Color;
84
+ currentMoveIndex: number;
85
+ isLatestMove: boolean;
86
+ info: {
87
+ turn: Color;
88
+ isPlayerTurn: boolean;
89
+ isOpponentTurn: boolean;
90
+ moveNumber: number;
91
+ lastMove: chess_js.Move | undefined;
92
+ isCheck: boolean;
93
+ isCheckmate: boolean;
94
+ isDraw: boolean;
95
+ isStalemate: boolean;
96
+ isThreefoldRepetition: boolean;
97
+ isInsufficientMaterial: boolean;
98
+ isGameOver: boolean;
99
+ isDrawn: boolean;
100
+ hasPlayerWon: boolean;
101
+ hasPlayerLost: boolean;
102
+ };
103
+ methods: {
104
+ makeMove: (move: Parameters<Chess["move"]>[0]) => boolean;
105
+ setPosition: (fen: string, orientation: Color) => void;
106
+ flipBoard: () => void;
107
+ goToMove: (moveIndex: number) => void;
108
+ goToStart: () => void;
109
+ goToEnd: () => void;
110
+ goToPreviousMove: () => void;
111
+ goToNextMove: () => void;
112
+ };
113
+ };
114
+
115
+ declare const useChessGameContext: () => {
116
+ game: chess_js.Chess;
117
+ currentFen: string;
118
+ currentPosition: string;
119
+ orientation: chess_js.Color;
120
+ currentMoveIndex: number;
121
+ isLatestMove: boolean;
122
+ info: {
123
+ turn: chess_js.Color;
124
+ isPlayerTurn: boolean;
125
+ isOpponentTurn: boolean;
126
+ moveNumber: number;
127
+ lastMove: chess_js.Move | undefined;
128
+ isCheck: boolean;
129
+ isCheckmate: boolean;
130
+ isDraw: boolean;
131
+ isStalemate: boolean;
132
+ isThreefoldRepetition: boolean;
133
+ isInsufficientMaterial: boolean;
134
+ isGameOver: boolean;
135
+ isDrawn: boolean;
136
+ hasPlayerWon: boolean;
137
+ hasPlayerLost: boolean;
138
+ };
139
+ methods: {
140
+ makeMove: (move: Parameters<chess_js.Chess["move"]>[0]) => boolean;
141
+ setPosition: (fen: string, orientation: chess_js.Color) => void;
142
+ flipBoard: () => void;
143
+ goToMove: (moveIndex: number) => void;
144
+ goToStart: () => void;
145
+ goToEnd: () => void;
146
+ goToPreviousMove: () => void;
147
+ goToNextMove: () => void;
148
+ };
149
+ };
150
+ type ChessGameContextType = ReturnType<typeof useChessGame>;
151
+
152
+ type KeyboardControlsProps = {
153
+ controls?: KeyboardControls;
154
+ };
155
+ type KeyboardControls = Record<string, (context: ChessGameContextType) => void>;
156
+ declare const KeyboardControls: React.FC<KeyboardControlsProps>;
157
+
158
+ declare const ChessGame: {
159
+ Root: React$1.FC<React$1.PropsWithChildren<RootProps>>;
160
+ Board: React$1.FC<ChessGameProps>;
161
+ Sounds: React$1.FC<SoundsProps>;
162
+ KeyboardControls: React$1.FC<{
163
+ controls?: KeyboardControls;
164
+ }>;
165
+ };
166
+
167
+ /**
168
+ * Returns an object with information about the current state of the game. This can be determined
169
+ * using chess.js instance, but this function is provided for convenience.
170
+ * @param game - The Chess.js instance representing the game.
171
+ * @returns An object with information about the current state of the game.
172
+ */
173
+ declare const getGameInfo: (game: Chess, orientation: Color) => {
174
+ turn: Color;
175
+ isPlayerTurn: boolean;
176
+ isOpponentTurn: boolean;
177
+ moveNumber: number;
178
+ lastMove: chess_js.Move | undefined;
179
+ isCheck: boolean;
180
+ isCheckmate: boolean;
181
+ isDraw: boolean;
182
+ isStalemate: boolean;
183
+ isThreefoldRepetition: boolean;
184
+ isInsufficientMaterial: boolean;
185
+ isGameOver: boolean;
186
+ isDrawn: boolean;
187
+ hasPlayerWon: boolean;
188
+ hasPlayerLost: boolean;
189
+ };
190
+ type GameInfo = ReturnType<typeof getGameInfo>;
191
+
192
+ /**
193
+ * Smart deep merge for ChessboardOptions that handles different property types appropriately:
194
+ * - Functions: Overwrite (custom functions replace base functions)
195
+ * - Objects: Deep merge (nested objects merge recursively)
196
+ * - Primitives: Overwrite (custom values replace base values)
197
+ *
198
+ * This ensures that computed options (like squareStyles with move highlighting) are preserved
199
+ * while allowing custom options to extend or override them intelligently.
200
+ *
201
+ * @param baseOptions - The computed base options (e.g., computed squareStyles, event handlers)
202
+ * @param customOptions - Custom options provided by the user
203
+ * @returns Intelligently merged ChessboardOptions
204
+ */
205
+ declare const deepMergeChessboardOptions: (baseOptions: ChessboardOptions, customOptions?: Partial<ChessboardOptions>) => ChessboardOptions;
206
+
207
+ /**
208
+ * Default theme for ChessGame component.
209
+ * These values match the original hardcoded colors for backward compatibility.
210
+ */
211
+ declare const defaultGameTheme: ChessGameTheme;
212
+
213
+ /**
214
+ * Lichess-inspired theme with green highlights
215
+ */
216
+ declare const lichessTheme: ChessGameTheme;
217
+ /**
218
+ * Chess.com-inspired theme with green board and yellow highlights
219
+ */
220
+ declare const chessComTheme: ChessGameTheme;
221
+
222
+ /**
223
+ * Deep merges a partial theme with the default theme.
224
+ * Allows users to override only specific theme properties while keeping defaults for the rest.
225
+ *
226
+ * @param partialTheme - Partial theme with only the properties to override
227
+ * @returns Complete theme with overridden properties merged with defaults
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const customTheme = mergeTheme({
232
+ * state: { lastMove: "rgba(100, 200, 100, 0.6)" }
233
+ * });
234
+ * // Returns full theme with only lastMove color changed
235
+ * ```
236
+ */
237
+ declare const mergeTheme: (partialTheme?: PartialChessGameTheme) => ChessGameTheme;
238
+ /**
239
+ * Deep merges a partial theme with a base theme.
240
+ * Useful when extending an existing theme.
241
+ *
242
+ * @param baseTheme - The base theme to extend
243
+ * @param partialTheme - Partial theme with properties to override
244
+ * @returns Complete theme with overridden properties
245
+ */
246
+ declare const mergeThemeWith: (baseTheme: ChessGameTheme, partialTheme?: PartialChessGameTheme) => ChessGameTheme;
247
+
248
+ /**
249
+ * Context for ChessGame theme
250
+ */
251
+ declare const ChessGameThemeContext: React__default.Context<ChessGameTheme>;
252
+ /**
253
+ * Hook to access the current ChessGame theme.
254
+ * Returns the default theme if no ThemeProvider is present.
255
+ */
256
+ declare const useChessGameTheme: () => ChessGameTheme;
257
+
258
+ declare const themes: {
259
+ readonly default: ChessGameTheme;
260
+ readonly lichess: ChessGameTheme;
261
+ readonly chessCom: ChessGameTheme;
262
+ };
263
+
264
+ export { type BoardTheme, ChessGame, type ChessGameContextType, type ChessGameProps, type ChessGameTheme, ChessGameThemeContext, type DeepPartial, type GameInfo, type IndicatorTheme, KeyboardControls, type PartialChessGameTheme, type RootProps, type Sound, type Sounds, type SoundsProps, type StateTheme, chessComTheme, deepMergeChessboardOptions, defaultGameTheme, lichessTheme, mergeTheme, mergeThemeWith, themes, useChessGame, useChessGameContext, type useChessGameProps, useChessGameTheme };
@@ -1,5 +1,5 @@
1
1
  // src/components/ChessGame/parts/Root.tsx
2
- import React3 from "react";
2
+ import React4 from "react";
3
3
 
4
4
  // src/hooks/useChessGame.ts
5
5
  import React, { useEffect } from "react";
@@ -9,9 +9,17 @@ import { Chess as Chess2 } from "chess.js";
9
9
  import { Chess } from "chess.js";
10
10
  import _ from "lodash";
11
11
  var cloneGame = (game) => {
12
- const copy = new Chess();
13
- copy.loadPgn(game.pgn());
14
- return copy;
12
+ try {
13
+ const copy = new Chess();
14
+ const pgn = game == null ? void 0 : game.pgn();
15
+ if (pgn) {
16
+ copy.loadPgn(pgn);
17
+ }
18
+ return copy;
19
+ } catch (e) {
20
+ console.error("Failed to clone game:", e);
21
+ return new Chess();
22
+ }
15
23
  };
16
24
  var getGameInfo = (game, orientation) => {
17
25
  const turn = game.turn();
@@ -57,6 +65,9 @@ var isLegalMove = (game, move) => {
57
65
  }
58
66
  };
59
67
  var requiresPromotion = (game, move) => {
68
+ if (!game) {
69
+ throw new Error("Game is required");
70
+ }
60
71
  try {
61
72
  const copy = cloneGame(game);
62
73
  const result = copy.move(move);
@@ -76,12 +87,20 @@ var getCurrentFen = (fen, game, currentMoveIndex) => {
76
87
  const tempGame = new Chess();
77
88
  if (currentMoveIndex === -1) {
78
89
  if (fen) {
79
- tempGame.load(fen);
90
+ try {
91
+ tempGame.load(fen);
92
+ } catch (e) {
93
+ console.error("Failed to load FEN in getCurrentFen:", fen, e);
94
+ }
80
95
  }
81
96
  } else {
82
97
  const moves = game.history().slice(0, currentMoveIndex + 1);
83
98
  if (fen) {
84
- tempGame.load(fen);
99
+ try {
100
+ tempGame.load(fen);
101
+ } catch (e) {
102
+ console.error("Failed to load FEN in getCurrentFen:", fen, e);
103
+ }
85
104
  }
86
105
  moves.forEach((move) => tempGame.move(move));
87
106
  }
@@ -93,9 +112,21 @@ var useChessGame = ({
93
112
  fen,
94
113
  orientation: initialOrientation
95
114
  } = {}) => {
96
- const [game, setGame] = React.useState(new Chess2(fen));
115
+ const [game, setGame] = React.useState(() => {
116
+ try {
117
+ return new Chess2(fen);
118
+ } catch (e) {
119
+ console.error("Invalid FEN:", fen, e);
120
+ return new Chess2();
121
+ }
122
+ });
97
123
  useEffect(() => {
98
- setGame(new Chess2(fen));
124
+ try {
125
+ setGame(new Chess2(fen));
126
+ } catch (e) {
127
+ console.error("Invalid FEN:", fen, e);
128
+ setGame(new Chess2());
129
+ }
99
130
  }, [fen]);
100
131
  const [orientation, setOrientation] = React.useState(
101
132
  initialOrientation ?? "w"
@@ -119,11 +150,15 @@ var useChessGame = ({
119
150
  [game, currentMoveIndex]
120
151
  );
121
152
  const setPosition = React.useCallback((fen2, orientation2) => {
122
- const newGame = new Chess2();
123
- newGame.load(fen2);
124
- setOrientation(orientation2);
125
- setGame(newGame);
126
- setCurrentMoveIndex(-1);
153
+ try {
154
+ const newGame = new Chess2();
155
+ newGame.load(fen2);
156
+ setOrientation(orientation2);
157
+ setGame(newGame);
158
+ setCurrentMoveIndex(-1);
159
+ } catch (e) {
160
+ console.error("Failed to load FEN:", fen2, e);
161
+ }
127
162
  }, []);
128
163
  const makeMove = React.useCallback(
129
164
  (move) => {
@@ -212,18 +247,68 @@ var useChessGameContext = () => {
212
247
  return context;
213
248
  };
214
249
 
250
+ // src/theme/context.tsx
251
+ import React3, { createContext, useContext } from "react";
252
+
253
+ // src/theme/defaults.ts
254
+ var defaultGameTheme = {
255
+ board: {
256
+ lightSquare: { backgroundColor: "#f0d9b5" },
257
+ darkSquare: { backgroundColor: "#b58863" }
258
+ },
259
+ state: {
260
+ lastMove: "rgba(255, 255, 0, 0.5)",
261
+ check: "rgba(255, 0, 0, 0.5)",
262
+ activeSquare: "rgba(255, 255, 0, 0.5)",
263
+ dropSquare: { backgroundColor: "rgba(255, 255, 0, 0.4)" }
264
+ },
265
+ indicators: {
266
+ move: "rgba(0, 0, 0, 0.1)",
267
+ capture: "rgba(1, 0, 0, 0.1)"
268
+ }
269
+ };
270
+
271
+ // src/theme/context.tsx
272
+ var ChessGameThemeContext = createContext(defaultGameTheme);
273
+ var useChessGameTheme = () => {
274
+ return useContext(ChessGameThemeContext);
275
+ };
276
+ var ThemeProvider = ({
277
+ theme,
278
+ children
279
+ }) => {
280
+ return /* @__PURE__ */ React3.createElement(ChessGameThemeContext.Provider, { value: theme }, children);
281
+ };
282
+
283
+ // src/theme/utils.ts
284
+ import { merge } from "lodash";
285
+ var mergeTheme = (partialTheme) => {
286
+ if (!partialTheme) {
287
+ return { ...defaultGameTheme };
288
+ }
289
+ return merge({}, defaultGameTheme, partialTheme);
290
+ };
291
+ var mergeThemeWith = (baseTheme, partialTheme) => {
292
+ if (!partialTheme) {
293
+ return { ...baseTheme };
294
+ }
295
+ return merge({}, baseTheme, partialTheme);
296
+ };
297
+
215
298
  // src/components/ChessGame/parts/Root.tsx
216
299
  var Root = ({
217
300
  fen,
218
301
  orientation,
302
+ theme,
219
303
  children
220
304
  }) => {
221
305
  const context = useChessGame({ fen, orientation });
222
- return /* @__PURE__ */ React3.createElement(ChessGameContext.Provider, { value: context }, children);
306
+ const mergedTheme = React4.useMemo(() => mergeTheme(theme), [theme]);
307
+ return /* @__PURE__ */ React4.createElement(ChessGameContext.Provider, { value: context }, /* @__PURE__ */ React4.createElement(ThemeProvider, { theme: mergedTheme }, children));
223
308
  };
224
309
 
225
310
  // src/components/ChessGame/parts/Board.tsx
226
- import React4 from "react";
311
+ import React5 from "react";
227
312
  import {
228
313
  Chessboard,
229
314
  defaultPieces,
@@ -231,23 +316,21 @@ import {
231
316
  } from "react-chessboard";
232
317
 
233
318
  // src/utils/board.ts
234
- import { merge } from "lodash";
235
- var LAST_MOVE_COLOR = "rgba(255, 255, 0, 0.5)";
236
- var CHECK_COLOR = "rgba(255, 0, 0, 0.5)";
237
- var getCustomSquareStyles = (game, info, activeSquare) => {
319
+ import { merge as merge2 } from "lodash";
320
+ var getCustomSquareStyles = (game, info, activeSquare, theme = defaultGameTheme) => {
238
321
  const customSquareStyles = {};
239
322
  const { lastMove, isCheck, turn } = info;
240
323
  if (lastMove) {
241
324
  customSquareStyles[lastMove.from] = {
242
- backgroundColor: LAST_MOVE_COLOR
325
+ backgroundColor: theme.state.lastMove
243
326
  };
244
327
  customSquareStyles[lastMove.to] = {
245
- backgroundColor: LAST_MOVE_COLOR
328
+ backgroundColor: theme.state.lastMove
246
329
  };
247
330
  }
248
331
  if (activeSquare) {
249
332
  customSquareStyles[activeSquare] = {
250
- backgroundColor: LAST_MOVE_COLOR
333
+ backgroundColor: theme.state.activeSquare
251
334
  };
252
335
  }
253
336
  if (activeSquare) {
@@ -255,7 +338,7 @@ var getCustomSquareStyles = (game, info, activeSquare) => {
255
338
  destinationSquares.forEach((square) => {
256
339
  var _a;
257
340
  customSquareStyles[square] = {
258
- background: game.get(square) && ((_a = game.get(square)) == null ? void 0 : _a.color) !== turn ? "radial-gradient(circle, rgba(1, 0, 0, 0.1) 85%, transparent 85%)" : "radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)"
341
+ background: game.get(square) && ((_a = game.get(square)) == null ? void 0 : _a.color) !== turn ? `radial-gradient(circle, ${theme.indicators.capture} 85%, transparent 85%)` : `radial-gradient(circle, ${theme.indicators.move} 25%, transparent 25%)`
259
342
  };
260
343
  });
261
344
  }
@@ -264,7 +347,7 @@ var getCustomSquareStyles = (game, info, activeSquare) => {
264
347
  return row.forEach((square) => {
265
348
  if ((square == null ? void 0 : square.type) === "k" && (square == null ? void 0 : square.color) === info.turn) {
266
349
  customSquareStyles[square.square] = {
267
- backgroundColor: CHECK_COLOR
350
+ backgroundColor: theme.state.check
268
351
  };
269
352
  }
270
353
  });
@@ -276,7 +359,7 @@ var deepMergeChessboardOptions = (baseOptions, customOptions) => {
276
359
  if (!customOptions) {
277
360
  return { ...baseOptions };
278
361
  }
279
- const result = merge({}, baseOptions, customOptions, {
362
+ const result = merge2({}, baseOptions, customOptions, {
280
363
  customizer: (_objValue, srcValue) => {
281
364
  if (typeof srcValue === "function") {
282
365
  return srcValue;
@@ -295,6 +378,7 @@ var deepMergeChessboardOptions = (baseOptions, customOptions) => {
295
378
  var Board = ({ options = {} }) => {
296
379
  var _a, _b, _c;
297
380
  const gameContext = useChessGameContext();
381
+ const theme = useChessGameTheme();
298
382
  if (!gameContext) {
299
383
  throw new Error("ChessGameContext not found");
300
384
  }
@@ -307,8 +391,8 @@ var Board = ({ options = {} }) => {
307
391
  methods: { makeMove }
308
392
  } = gameContext;
309
393
  const { turn, isGameOver } = info;
310
- const [activeSquare, setActiveSquare] = React4.useState(null);
311
- const [promotionMove, setPromotionMove] = React4.useState(null);
394
+ const [activeSquare, setActiveSquare] = React5.useState(null);
395
+ const [promotionMove, setPromotionMove] = React5.useState(null);
312
396
  const onSquareClick = (square) => {
313
397
  if (isGameOver) {
314
398
  return;
@@ -357,13 +441,13 @@ var Board = ({ options = {} }) => {
357
441
  setActiveSquare(null);
358
442
  setPromotionMove(null);
359
443
  };
360
- const squareWidth = React4.useMemo(() => {
444
+ const squareWidth = React5.useMemo(() => {
361
445
  var _a2;
362
446
  if (typeof document === "undefined") return 80;
363
447
  const squareElement = document.querySelector(`[data-square]`);
364
448
  return ((_a2 = squareElement == null ? void 0 : squareElement.getBoundingClientRect()) == null ? void 0 : _a2.width) ?? 80;
365
449
  }, [promotionMove]);
366
- const promotionSquareLeft = React4.useMemo(() => {
450
+ const promotionSquareLeft = React5.useMemo(() => {
367
451
  var _a2;
368
452
  if (!(promotionMove == null ? void 0 : promotionMove.to)) return 0;
369
453
  const column = ((_a2 = promotionMove.to.match(/^[a-h]/)) == null ? void 0 : _a2[0]) ?? "a";
@@ -374,18 +458,18 @@ var Board = ({ options = {} }) => {
374
458
  );
375
459
  }, [promotionMove, squareWidth, orientation]);
376
460
  const baseOptions = {
377
- squareStyles: getCustomSquareStyles(game, info, activeSquare),
461
+ squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
378
462
  boardOrientation: orientation === "b" ? "black" : "white",
379
463
  position: currentFen,
380
464
  showNotation: true,
381
465
  showAnimations: isLatestMove,
466
+ lightSquareStyle: theme.board.lightSquare,
467
+ darkSquareStyle: theme.board.darkSquare,
382
468
  canDragPiece: ({ piece }) => {
383
469
  if (isGameOver) return false;
384
470
  return piece.pieceType[0] === turn;
385
471
  },
386
- dropSquareStyle: {
387
- backgroundColor: "rgba(255, 255, 0, 0.4)"
388
- },
472
+ dropSquareStyle: theme.state.dropSquare,
389
473
  onPieceDrag: ({ piece, square }) => {
390
474
  if (piece.pieceType[0] === turn) {
391
475
  setActiveSquare(square);
@@ -413,7 +497,7 @@ var Board = ({ options = {} }) => {
413
497
  animationDurationInMs: game.history().length === 0 ? 0 : 300
414
498
  };
415
499
  const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
416
- return /* @__PURE__ */ React4.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React4.createElement(Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(
500
+ return /* @__PURE__ */ React5.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React5.createElement(Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(
417
501
  "div",
418
502
  {
419
503
  onClick: () => setPromotionMove(null),
@@ -431,7 +515,7 @@ var Board = ({ options = {} }) => {
431
515
  zIndex: 1e3
432
516
  }
433
517
  }
434
- ), /* @__PURE__ */ React4.createElement(
518
+ ), /* @__PURE__ */ React5.createElement(
435
519
  "div",
436
520
  {
437
521
  style: {
@@ -447,7 +531,7 @@ var Board = ({ options = {} }) => {
447
531
  boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.5)"
448
532
  }
449
533
  },
450
- ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ React4.createElement(
534
+ ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ React5.createElement(
451
535
  "button",
452
536
  {
453
537
  key: piece,
@@ -589,9 +673,59 @@ var ChessGame = {
589
673
  Sounds,
590
674
  KeyboardControls: KeyboardControls2
591
675
  };
676
+
677
+ // src/theme/presets.ts
678
+ var lichessTheme = {
679
+ board: {
680
+ lightSquare: { backgroundColor: "#f0d9b5" },
681
+ darkSquare: { backgroundColor: "#b58863" }
682
+ },
683
+ state: {
684
+ lastMove: "rgba(155, 199, 0, 0.41)",
685
+ check: "rgba(255, 0, 0, 0.5)",
686
+ activeSquare: "rgba(20, 85, 30, 0.5)",
687
+ dropSquare: { backgroundColor: "rgba(20, 85, 30, 0.3)" }
688
+ },
689
+ indicators: {
690
+ move: "rgba(20, 85, 30, 0.3)",
691
+ capture: "rgba(20, 85, 30, 0.3)"
692
+ }
693
+ };
694
+ var chessComTheme = {
695
+ board: {
696
+ lightSquare: { backgroundColor: "#ebecd0" },
697
+ darkSquare: { backgroundColor: "#779556" }
698
+ },
699
+ state: {
700
+ lastMove: "rgba(255, 255, 0, 0.5)",
701
+ check: "rgba(255, 0, 0, 0.7)",
702
+ activeSquare: "rgba(255, 255, 0, 0.5)",
703
+ dropSquare: { backgroundColor: "rgba(255, 255, 0, 0.4)" }
704
+ },
705
+ indicators: {
706
+ move: "rgba(0, 0, 0, 0.1)",
707
+ capture: "rgba(0, 0, 0, 0.1)"
708
+ }
709
+ };
710
+
711
+ // src/theme/index.ts
712
+ var themes = {
713
+ default: defaultGameTheme,
714
+ lichess: lichessTheme,
715
+ chessCom: chessComTheme
716
+ };
592
717
  export {
593
718
  ChessGame,
719
+ ChessGameThemeContext,
720
+ chessComTheme,
721
+ deepMergeChessboardOptions,
722
+ defaultGameTheme,
723
+ lichessTheme,
724
+ mergeTheme,
725
+ mergeThemeWith,
726
+ themes,
594
727
  useChessGame,
595
- useChessGameContext
728
+ useChessGameContext,
729
+ useChessGameTheme
596
730
  };
597
- //# sourceMappingURL=index.mjs.map
731
+ //# sourceMappingURL=index.js.map