@react-chess-tools/react-chess-game 0.3.1 → 0.4.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 (34) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.MD +42 -35
  3. package/coverage/clover.xml +6 -0
  4. package/coverage/coverage-final.json +1 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +101 -0
  9. package/coverage/lcov-report/prettify.css +1 -0
  10. package/coverage/lcov-report/prettify.js +2 -0
  11. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  12. package/coverage/lcov-report/sorter.js +196 -0
  13. package/coverage/lcov.info +0 -0
  14. package/dist/index.d.mts +85 -27
  15. package/dist/index.mjs +180 -37
  16. package/dist/index.mjs.map +1 -1
  17. package/package.json +2 -1
  18. package/src/components/ChessGame/ChessGame.stories.tsx +18 -0
  19. package/src/components/ChessGame/index.ts +2 -0
  20. package/src/components/ChessGame/parts/Board.tsx +2 -1
  21. package/src/components/ChessGame/parts/KeyboardControls.tsx +33 -0
  22. package/src/components/ChessGame/parts/Sounds.tsx +8 -2
  23. package/src/hooks/__tests__/useChessGame.test.tsx +230 -0
  24. package/src/hooks/useBoardSounds.test.ts +196 -0
  25. package/src/hooks/useBoardSounds.ts +8 -3
  26. package/src/hooks/useChessGame.ts +103 -22
  27. package/src/hooks/useChessGameContext.ts +2 -0
  28. package/src/hooks/useKeyboardControls.test.tsx +171 -0
  29. package/src/hooks/useKeyboardControls.ts +28 -0
  30. package/src/index.ts +19 -0
  31. package/src/utils/__tests__/board.test.ts +143 -0
  32. package/src/utils/__tests__/chess.test.ts +198 -0
  33. package/src/utils/board.ts +2 -2
  34. package/src/utils/chess.ts +32 -8
@@ -0,0 +1,196 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { type Move, type PieceSymbol } from "chess.js";
3
+ import { useBoardSounds } from "./useBoardSounds";
4
+ import { useChessGameContext } from "./useChessGameContext";
5
+ import { type Sound } from "../assets/sounds";
6
+
7
+ // Mock the context hook
8
+ jest.mock("./useChessGameContext");
9
+ const mockedUseChessGameContext = useChessGameContext as jest.MockedFunction<
10
+ typeof useChessGameContext
11
+ >;
12
+
13
+ // Helper to create mock audio elements
14
+ const createMockSounds = (): Record<Sound, HTMLAudioElement> => ({
15
+ move: { play: jest.fn() } as unknown as HTMLAudioElement,
16
+ capture: { play: jest.fn() } as unknown as HTMLAudioElement,
17
+ gameOver: { play: jest.fn() } as unknown as HTMLAudioElement,
18
+ check: { play: jest.fn() } as unknown as HTMLAudioElement,
19
+ });
20
+
21
+ describe("useBoardSounds", () => {
22
+ let mockSounds: Record<Sound, HTMLAudioElement>;
23
+ let mockContextValue: {
24
+ info: {
25
+ lastMove?: Partial<Move> | null;
26
+ isCheckmate?: boolean;
27
+ };
28
+ };
29
+
30
+ beforeEach(() => {
31
+ // Reset mocks before each test
32
+ jest.clearAllMocks();
33
+ mockSounds = createMockSounds();
34
+ mockContextValue = {
35
+ info: {
36
+ lastMove: null,
37
+ isCheckmate: false,
38
+ },
39
+ };
40
+ mockedUseChessGameContext.mockReturnValue(
41
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
42
+ );
43
+ });
44
+
45
+ it("should not play any sound initially", () => {
46
+ renderHook(() => useBoardSounds(mockSounds));
47
+ expect(mockSounds.move.play).not.toHaveBeenCalled();
48
+ expect(mockSounds.capture.play).not.toHaveBeenCalled();
49
+ expect(mockSounds.gameOver.play).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it("should play move sound when lastMove is present", () => {
53
+ mockContextValue.info.lastMove = {
54
+ from: "e2",
55
+ to: "e4",
56
+ piece: "P" as PieceSymbol,
57
+ } as unknown as Partial<Move>;
58
+ const { rerender } = renderHook(() => useBoardSounds(mockSounds));
59
+
60
+ mockedUseChessGameContext.mockReturnValue(
61
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
62
+ );
63
+ rerender();
64
+
65
+ expect(mockSounds.move.play).toHaveBeenCalledTimes(1);
66
+ expect(mockSounds.capture.play).not.toHaveBeenCalled();
67
+ expect(mockSounds.gameOver.play).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it("should play capture sound when lastMove includes a capture", () => {
71
+ mockContextValue.info.lastMove = {
72
+ from: "e4",
73
+ to: "d5",
74
+ piece: "P" as PieceSymbol,
75
+ captured: "p" as PieceSymbol,
76
+ } as unknown as Partial<Move>;
77
+ mockedUseChessGameContext.mockReturnValue(
78
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
79
+ );
80
+
81
+ const { rerender } = renderHook(() => useBoardSounds(mockSounds));
82
+ rerender();
83
+
84
+ expect(mockSounds.capture.play).toHaveBeenCalledTimes(1);
85
+ expect(mockSounds.move.play).not.toHaveBeenCalled();
86
+ expect(mockSounds.gameOver.play).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it("should play gameOver sound when isCheckmate is true", () => {
90
+ mockContextValue.info.isCheckmate = true;
91
+ mockContextValue.info.lastMove = {
92
+ from: "f3",
93
+ to: "g5",
94
+ piece: "Q" as PieceSymbol,
95
+ } as unknown as Partial<Move>;
96
+ mockedUseChessGameContext.mockReturnValue(
97
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
98
+ );
99
+
100
+ const { rerender } = renderHook(() => useBoardSounds(mockSounds));
101
+ rerender();
102
+
103
+ expect(mockSounds.gameOver.play).toHaveBeenCalledTimes(1);
104
+ expect(mockSounds.move.play).not.toHaveBeenCalled();
105
+ expect(mockSounds.capture.play).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it("should play gameOver sound even if last move was a capture", () => {
109
+ mockContextValue.info.isCheckmate = true;
110
+ mockContextValue.info.lastMove = {
111
+ from: "f3",
112
+ to: "g7",
113
+ piece: "Q" as PieceSymbol,
114
+ captured: "p" as PieceSymbol,
115
+ } as unknown as Partial<Move>;
116
+ mockedUseChessGameContext.mockReturnValue(
117
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
118
+ );
119
+
120
+ const { rerender } = renderHook(() => useBoardSounds(mockSounds));
121
+ rerender();
122
+
123
+ expect(mockSounds.gameOver.play).toHaveBeenCalledTimes(1);
124
+ expect(mockSounds.capture.play).not.toHaveBeenCalled();
125
+ expect(mockSounds.move.play).not.toHaveBeenCalled();
126
+ });
127
+
128
+ it("should not play sound if lastMove becomes null", () => {
129
+ mockContextValue.info.lastMove = {
130
+ from: "e2",
131
+ to: "e4",
132
+ piece: "P" as PieceSymbol,
133
+ } as unknown as Partial<Move>;
134
+ mockedUseChessGameContext.mockReturnValue(
135
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
136
+ );
137
+ const { rerender } = renderHook(() => useBoardSounds(mockSounds));
138
+ rerender();
139
+ expect(mockSounds.move.play).toHaveBeenCalledTimes(1);
140
+
141
+ mockContextValue.info.lastMove = null;
142
+ mockedUseChessGameContext.mockReturnValue(
143
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
144
+ );
145
+ rerender();
146
+
147
+ expect(mockSounds.move.play).toHaveBeenCalledTimes(1);
148
+ expect(mockSounds.capture.play).not.toHaveBeenCalled();
149
+ expect(mockSounds.gameOver.play).not.toHaveBeenCalled();
150
+ });
151
+
152
+ it("should use updated sounds when they change", () => {
153
+ // Setup initial sounds and render hook
154
+ mockContextValue.info.lastMove = {
155
+ from: "e2",
156
+ to: "e4",
157
+ piece: "P" as PieceSymbol,
158
+ } as unknown as Partial<Move>;
159
+ mockedUseChessGameContext.mockReturnValue(
160
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
161
+ );
162
+
163
+ const { rerender } = renderHook((props) => useBoardSounds(props), {
164
+ initialProps: mockSounds,
165
+ });
166
+
167
+ // Verify initial sound played
168
+ expect(mockSounds.move.play).toHaveBeenCalledTimes(1);
169
+
170
+ // Create new set of mock sounds
171
+ const newMockSounds = createMockSounds();
172
+
173
+ // Re-render with new sounds and trigger a move
174
+ rerender(newMockSounds);
175
+
176
+ // Update lastMove to trigger sound effect with new sounds
177
+ mockContextValue.info.lastMove = {
178
+ from: "e7",
179
+ to: "e5",
180
+ piece: "P" as PieceSymbol,
181
+ } as unknown as Partial<Move>;
182
+ mockedUseChessGameContext.mockReturnValue(
183
+ mockContextValue as unknown as ReturnType<typeof useChessGameContext>,
184
+ );
185
+
186
+ rerender(newMockSounds);
187
+
188
+ // Original sounds should not be called again
189
+ expect(mockSounds.move.play).toHaveBeenCalledTimes(1);
190
+
191
+ // New sounds should be called
192
+ expect(newMockSounds.move.play).toHaveBeenCalledTimes(1);
193
+ expect(newMockSounds.capture.play).not.toHaveBeenCalled();
194
+ expect(newMockSounds.gameOver.play).not.toHaveBeenCalled();
195
+ });
196
+ });
@@ -6,17 +6,22 @@ export const useBoardSounds = (sounds: Record<Sound, HTMLAudioElement>) => {
6
6
  const {
7
7
  info: { lastMove, isCheckmate },
8
8
  } = useChessGameContext();
9
+
9
10
  useEffect(() => {
11
+ if (Object.keys(sounds).length === 0) {
12
+ return;
13
+ }
14
+
10
15
  if (isCheckmate) {
11
- sounds.gameOver.play();
16
+ sounds.gameOver?.play();
12
17
  return;
13
18
  }
14
19
  if (lastMove?.captured) {
15
- sounds.capture.play();
20
+ sounds.capture?.play();
16
21
  return;
17
22
  }
18
23
  if (lastMove) {
19
- sounds.move.play();
24
+ sounds.move?.play();
20
25
  return;
21
26
  }
22
27
  }, [lastMove]);
@@ -1,6 +1,6 @@
1
- import React from "react";
1
+ import React, { useEffect } from "react";
2
2
  import { Chess, Color } from "chess.js";
3
- import { cloneGame, getGameInfo } from "../utils/chess";
3
+ import { cloneGame, getCurrentFen, getGameInfo } from "../utils/chess";
4
4
 
5
5
  export type useChessGameProps = {
6
6
  fen?: string;
@@ -12,41 +12,122 @@ export const useChessGame = ({
12
12
  orientation: initialOrientation,
13
13
  }: useChessGameProps = {}) => {
14
14
  const [game, setGame] = React.useState(new Chess(fen));
15
+
16
+ useEffect(() => {
17
+ setGame(new Chess(fen));
18
+ }, [fen]);
19
+
15
20
  const [orientation, setOrientation] = React.useState<Color>(
16
21
  initialOrientation ?? "w",
17
22
  );
23
+ const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);
18
24
 
19
- const setPosition = (fen: string, orientation: Color) => {
25
+ const history = React.useMemo(() => game.history(), [game]);
26
+ const isLatestMove = React.useMemo(
27
+ () => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,
28
+ [currentMoveIndex, history.length],
29
+ );
30
+
31
+ const info = React.useMemo(
32
+ () => getGameInfo(game, orientation),
33
+ [game, orientation],
34
+ );
35
+
36
+ const currentFen = React.useMemo(
37
+ () => getCurrentFen(fen, game, currentMoveIndex),
38
+ [game, currentMoveIndex],
39
+ );
40
+
41
+ const currentPosition = React.useMemo(
42
+ () => game.history()[currentMoveIndex],
43
+ [game, currentMoveIndex],
44
+ );
45
+
46
+ const setPosition = React.useCallback((fen: string, orientation: Color) => {
20
47
  const newGame = new Chess();
21
48
  newGame.load(fen);
22
49
  setOrientation(orientation);
23
50
  setGame(newGame);
24
- };
51
+ setCurrentMoveIndex(-1);
52
+ }, []);
25
53
 
26
- const makeMove = (move: Parameters<Chess["move"]>[0]): boolean => {
27
- try {
28
- const copy = cloneGame(game);
29
- copy.move(move);
30
- setGame(copy);
54
+ const makeMove = React.useCallback(
55
+ (move: Parameters<Chess["move"]>[0]): boolean => {
56
+ // Only allow moves when we're at the latest position
57
+ if (!isLatestMove) {
58
+ return false;
59
+ }
31
60
 
32
- return true;
33
- } catch (e) {
34
- return false;
35
- }
36
- };
61
+ try {
62
+ const copy = cloneGame(game);
63
+ copy.move(move);
64
+ setGame(copy);
65
+ setCurrentMoveIndex(copy.history().length - 1);
66
+ return true;
67
+ } catch (e) {
68
+ return false;
69
+ }
70
+ },
71
+ [isLatestMove, game],
72
+ );
37
73
 
38
- const flipBoard = () => {
74
+ const flipBoard = React.useCallback(() => {
39
75
  setOrientation((orientation) => (orientation === "w" ? "b" : "w"));
40
- };
76
+ }, []);
41
77
 
42
- return {
43
- game,
44
- orientation,
45
- info: getGameInfo(game, orientation),
46
- methods: {
78
+ const goToMove = React.useCallback(
79
+ (moveIndex: number) => {
80
+ if (moveIndex < -1 || moveIndex >= history.length) return;
81
+ setCurrentMoveIndex(moveIndex);
82
+ },
83
+ [history.length],
84
+ );
85
+
86
+ const goToStart = React.useCallback(() => goToMove(-1), []);
87
+ const goToEnd = React.useCallback(
88
+ () => goToMove(history.length - 1),
89
+ [history.length],
90
+ );
91
+ const goToPreviousMove = React.useCallback(
92
+ () => goToMove(currentMoveIndex - 1),
93
+ [currentMoveIndex],
94
+ );
95
+ const goToNextMove = React.useCallback(
96
+ () => goToMove(currentMoveIndex + 1),
97
+ [currentMoveIndex],
98
+ );
99
+
100
+ const methods = React.useMemo(
101
+ () => ({
47
102
  makeMove,
48
103
  setPosition,
49
104
  flipBoard,
50
- },
105
+ goToMove,
106
+ goToStart,
107
+ goToEnd,
108
+ goToPreviousMove,
109
+ goToNextMove,
110
+ }),
111
+ [
112
+ makeMove,
113
+ setPosition,
114
+ flipBoard,
115
+ goToMove,
116
+ goToStart,
117
+ goToEnd,
118
+ goToPreviousMove,
119
+ goToNextMove,
120
+ ],
121
+ );
122
+
123
+ return {
124
+ game,
125
+ currentFen,
126
+ currentPosition,
127
+ orientation,
128
+ currentMoveIndex,
129
+ isLatestMove,
130
+ info,
131
+ methods,
51
132
  };
52
133
  };
@@ -14,3 +14,5 @@ export const useChessGameContext = () => {
14
14
  }
15
15
  return context;
16
16
  };
17
+
18
+ export type ChessGameContextType = ReturnType<typeof useChessGame>;
@@ -0,0 +1,171 @@
1
+ import { renderHook, act } from "@testing-library/react";
2
+ import { useKeyboardControls } from "./useKeyboardControls";
3
+ import {
4
+ useChessGameContext,
5
+ ChessGameContextType,
6
+ } from "./useChessGameContext";
7
+ import { Chess, Color } from "chess.js";
8
+
9
+ // Mock the context hook
10
+ jest.mock("./useChessGameContext");
11
+ const mockUseChessGameContext = useChessGameContext as jest.MockedFunction<
12
+ typeof useChessGameContext
13
+ >;
14
+
15
+ describe("useKeyboardControls", () => {
16
+ let mockGameContext: jest.Mocked<ChessGameContextType>;
17
+ let addEventListenerSpy: jest.SpyInstance;
18
+ let removeEventListenerSpy: jest.SpyInstance;
19
+
20
+ beforeEach(() => {
21
+ // Reset mocks and spies
22
+ jest.clearAllMocks();
23
+ mockGameContext = {
24
+ game: {
25
+ /* Add minimal Chess mock properties/methods if needed */
26
+ } as jest.Mocked<Chess>,
27
+ currentFen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
28
+ currentPosition: "",
29
+ orientation: "w" as Color,
30
+ currentMoveIndex: -1,
31
+ isLatestMove: true,
32
+ info: {
33
+ /* Add minimal GameInfo mock properties/methods if needed */
34
+ } as jest.Mocked<ChessGameContextType["info"]>,
35
+ methods: {
36
+ undo: jest.fn(),
37
+ redo: jest.fn(),
38
+ makeMove: jest.fn(),
39
+ setPosition: jest.fn(),
40
+ flipBoard: jest.fn(),
41
+ goToMove: jest.fn(),
42
+ goToStart: jest.fn(),
43
+ goToEnd: jest.fn(),
44
+ goToPreviousMove: jest.fn(),
45
+ goToNextMove: jest.fn(),
46
+ },
47
+ } as unknown as jest.Mocked<ChessGameContextType>;
48
+
49
+ mockUseChessGameContext.mockReturnValue(mockGameContext);
50
+
51
+ addEventListenerSpy = jest.spyOn(window, "addEventListener");
52
+ removeEventListenerSpy = jest.spyOn(window, "removeEventListener");
53
+ });
54
+
55
+ afterEach(() => {
56
+ // Restore original implementations
57
+ addEventListenerSpy.mockRestore();
58
+ removeEventListenerSpy.mockRestore();
59
+ });
60
+
61
+ it("should add keydown event listener on mount and remove on unmount", () => {
62
+ const { unmount } = renderHook(() => useKeyboardControls());
63
+ expect(addEventListenerSpy).toHaveBeenCalledWith(
64
+ "keydown",
65
+ expect.any(Function),
66
+ );
67
+ expect(removeEventListenerSpy).not.toHaveBeenCalled();
68
+
69
+ unmount();
70
+ expect(removeEventListenerSpy).toHaveBeenCalledWith(
71
+ "keydown",
72
+ expect.any(Function),
73
+ );
74
+ // Ensure the listener functions are the same instance
75
+ expect(addEventListenerSpy.mock.calls[0][1]).toBe(
76
+ removeEventListenerSpy.mock.calls[0][1],
77
+ );
78
+ });
79
+
80
+ it("should call the default handler for a default key", () => {
81
+ renderHook(() => useKeyboardControls());
82
+ const event = new KeyboardEvent("keydown", { key: "ArrowLeft" });
83
+ const preventDefaultSpy = jest.spyOn(event, "preventDefault");
84
+
85
+ act(() => {
86
+ window.dispatchEvent(event);
87
+ });
88
+
89
+ const undoHandler = mockGameContext.methods.goToPreviousMove;
90
+
91
+ expect(undoHandler).toHaveBeenCalledTimes(1);
92
+
93
+ expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("should call the custom handler if provided", () => {
97
+ const customHandler = jest.fn();
98
+ const customControls = { z: customHandler };
99
+
100
+ renderHook(() => useKeyboardControls(customControls));
101
+
102
+ const event = new KeyboardEvent("keydown", { key: "z" });
103
+ const preventDefaultSpy = jest.spyOn(event, "preventDefault");
104
+
105
+ act(() => {
106
+ window.dispatchEvent(event);
107
+ });
108
+
109
+ expect(customHandler).toHaveBeenCalledWith(mockGameContext);
110
+ expect(customHandler).toHaveBeenCalledTimes(1);
111
+ expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
112
+ });
113
+
114
+ it("should call the default handler if a custom handler for a different key is provided", () => {
115
+ const customHandler = jest.fn();
116
+ const customControls = { z: customHandler };
117
+
118
+ renderHook(() => useKeyboardControls(customControls));
119
+
120
+ const event = new KeyboardEvent("keydown", { key: "ArrowRight" });
121
+ const preventDefaultSpy = jest.spyOn(event, "preventDefault");
122
+
123
+ const redoHandler = mockGameContext.methods.goToNextMove;
124
+
125
+ act(() => {
126
+ window.dispatchEvent(event);
127
+ });
128
+
129
+ expect(redoHandler).toHaveBeenCalledTimes(1);
130
+
131
+ expect(customHandler).not.toHaveBeenCalled();
132
+ expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
133
+ });
134
+
135
+ it("should override the default handler if a custom handler for the same key is provided", () => {
136
+ const customArrowLeftHandler = jest.fn();
137
+ const customControls = { ArrowLeft: customArrowLeftHandler };
138
+
139
+ renderHook(() => useKeyboardControls(customControls));
140
+
141
+ const event = new KeyboardEvent("keydown", { key: "ArrowLeft" });
142
+ const preventDefaultSpy = jest.spyOn(event, "preventDefault");
143
+
144
+ act(() => {
145
+ window.dispatchEvent(event);
146
+ });
147
+
148
+ expect(customArrowLeftHandler).toHaveBeenCalledWith(mockGameContext);
149
+ expect(customArrowLeftHandler).toHaveBeenCalledTimes(1);
150
+ expect(mockGameContext.methods.goToPreviousMove).not.toHaveBeenCalled();
151
+ expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
152
+ });
153
+
154
+ it("should do nothing if an unmapped key is pressed", () => {
155
+ renderHook(() => useKeyboardControls());
156
+ const event = new KeyboardEvent("keydown", { key: "UnmappedKey" });
157
+ const preventDefaultSpy = jest.spyOn(event, "preventDefault");
158
+
159
+ act(() => {
160
+ window.dispatchEvent(event);
161
+ });
162
+
163
+ // Check that no context functions were called
164
+ Object.values(mockGameContext.methods).forEach((mockFn) => {
165
+ if (jest.isMockFunction(mockFn)) {
166
+ expect(mockFn).not.toHaveBeenCalled();
167
+ }
168
+ });
169
+ expect(preventDefaultSpy).not.toHaveBeenCalled();
170
+ });
171
+ });
@@ -0,0 +1,28 @@
1
+ import { useEffect } from "react";
2
+ import {
3
+ defaultKeyboardControls,
4
+ KeyboardControls,
5
+ } from "../components/ChessGame/parts/KeyboardControls";
6
+ import { useChessGameContext } from "./useChessGameContext";
7
+
8
+ export const useKeyboardControls = (controls?: KeyboardControls) => {
9
+ const gameContext = useChessGameContext();
10
+ if (!gameContext) {
11
+ throw new Error("ChessGameContext not found");
12
+ }
13
+ const keyboardControls = { ...defaultKeyboardControls, ...controls };
14
+ useEffect(() => {
15
+ const handleKeyDown = (event: KeyboardEvent) => {
16
+ const handler = keyboardControls[event.key];
17
+ if (handler) {
18
+ event.preventDefault();
19
+ handler(gameContext);
20
+ }
21
+ };
22
+ window.addEventListener("keydown", handleKeyDown);
23
+ return () => {
24
+ window.removeEventListener("keydown", handleKeyDown);
25
+ };
26
+ }, [gameContext]);
27
+ return null;
28
+ };
package/src/index.ts CHANGED
@@ -1,3 +1,22 @@
1
+ // Components
1
2
  export { ChessGame } from "./components/ChessGame";
3
+
4
+ // Hooks & Context
2
5
  export { useChessGameContext } from "./hooks/useChessGameContext";
3
6
  export { useChessGame } from "./hooks/useChessGame";
7
+ export type { ChessGameContextType } from "./hooks/useChessGameContext";
8
+ export type { useChessGameProps } from "./hooks/useChessGame";
9
+
10
+ // Audio Types
11
+ export type { Sound, Sounds } from "./assets/sounds";
12
+ export type { SoundsProps } from "./components/ChessGame/parts/Sounds";
13
+
14
+ // Keyboard Types
15
+ export type { KeyboardControls } from "./components/ChessGame/parts/KeyboardControls";
16
+
17
+ // Utility Types
18
+ export type { GameInfo } from "./utils/chess";
19
+
20
+ // Component Props
21
+ export type { ChessGameProps } from "./components/ChessGame/parts/Board";
22
+ export type { RootProps } from "./components/ChessGame/parts/Root";