@react-chess-tools/react-chess-game 1.0.1 → 1.0.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @react-chess-tools/react-chess-game
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 1c4f876: docs: update README files
8
+ - e678d58: chore: update dependencies
9
+ - Updated dependencies [1c4f876]
10
+ - Updated dependencies [e678d58]
11
+ - @react-chess-tools/react-chess-clock@1.0.2
12
+
13
+ ## 1.0.2
14
+
15
+ ### Patch Changes
16
+
17
+ - 93e1029: feat: add chess clock
18
+ - Updated dependencies [93e1029]
19
+ - @react-chess-tools/react-chess-clock@1.0.1
20
+
3
21
  ## 1.0.1
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
  - [ChessGame.Board](#chessgameboard)
26
26
  - [ChessGame.Sounds](#chessgamesounds)
27
27
  - [ChessGame.KeyboardControls](#chessgamekeyboardcontrols)
28
+ - [ChessGame.Clock](#chessgameclock)
28
29
  - [Hooks](#hooks)
29
30
  - [useChessGameContext](#usechessgamecontext)
30
31
  - [Examples](#examples)
@@ -36,12 +37,15 @@
36
37
 
37
38
  Built using a compound component pattern (similar to [Radix UI](https://www.radix-ui.com/)), it provides a `ChessGameContext` that you can use to customize and enhance the game while maintaining sensible defaults.
38
39
 
40
+ The package now includes integrated chess clock functionality powered by [`@react-chess-tools/react-chess-clock`](https://www.npmjs.com/package/@react-chess-tools/react-chess-clock).
41
+
39
42
  ## Features
40
43
 
41
44
  - **Move-by-click** - Click to select and move pieces
42
45
  - **Sound effects** - Built-in sounds for moves, captures, check, and game over
43
46
  - **Square highlighting** - Visual feedback for valid moves and last move
44
47
  - **Keyboard controls** - Navigate through game history with arrow keys
48
+ - **Integrated Chess Clock** - Built-in clock support with multiple timing methods
45
49
  - **Full game state** - Access to check, checkmate, stalemate, draw detection
46
50
  - **TypeScript** - Full TypeScript support with comprehensive type definitions
47
51
  - **Customizable** - Override any default with your own implementation
@@ -76,6 +80,23 @@ function App() {
76
80
  }
77
81
  ```
78
82
 
83
+ With a chess clock:
84
+
85
+ ```tsx
86
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
87
+
88
+ function App() {
89
+ return (
90
+ <ChessGame.Root timeControl={{ time: "5+3" }}>
91
+ <ChessGame.Clock.Display color="white" />
92
+ <ChessGame.Board />
93
+ <ChessGame.Clock.Display color="black" />
94
+ <ChessGame.Clock.PlayPause />
95
+ </ChessGame.Root>
96
+ );
97
+ }
98
+ ```
99
+
79
100
  ## Demo
80
101
 
81
102
  Visit the [live demo](https://react-chess-tools.vercel.app/) to see the component in action.
@@ -84,18 +105,20 @@ Visit the [live demo](https://react-chess-tools.vercel.app/) to see the componen
84
105
 
85
106
  ### ChessGame.Root
86
107
 
87
- The root component that provides `ChessGameContext` to all child components. It instantiates a `Chess` instance using the `fen` prop.
108
+ The root component that provides `ChessGameContext` to all child components. It instantiates a `Chess` instance using the `fen` prop and optionally sets up a chess clock.
88
109
 
89
110
  **Note:** This is a logic-only component (Context Provider). It does not render any DOM elements.
90
111
 
91
112
  #### Props
92
113
 
93
- | Name | Type | Default | Description |
94
- | ------------- | ----------------------- | ----------------- | -------------------------------------------- |
95
- | `children` | `ReactNode` | - | Child components |
96
- | `fen` | `string` | Starting position | Initial FEN string for the chess game |
97
- | `orientation` | `"w" \| "b"` | `"w"` | Board orientation (white or black at bottom) |
98
- | `theme` | `PartialChessGameTheme` | - | Optional theme configuration |
114
+ | Name | Type | Default | Description |
115
+ | ------------------ | ----------------------- | ----------------- | -------------------------------------------------------- |
116
+ | `children` | `ReactNode` | - | Child components |
117
+ | `fen` | `string` | Starting position | Initial FEN string for the chess game |
118
+ | `orientation` | `"w" \| "b"` | `"w"` | Board orientation (white or black at bottom) |
119
+ | `theme` | `PartialChessGameTheme` | - | Optional theme configuration |
120
+ | `timeControl` | `TimeControlConfig` | - | Optional clock configuration to enable chess clock |
121
+ | `autoSwitchOnMove` | `boolean` | `true` | Auto-switch clock on move (when timeControl is provided) |
99
122
 
100
123
  #### Example
101
124
 
@@ -103,6 +126,7 @@ The root component that provides `ChessGameContext` to all child components. It
103
126
  <ChessGame.Root
104
127
  fen="rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
105
128
  orientation="b"
129
+ timeControl={{ time: "10+5" }}
106
130
  >
107
131
  <ChessGame.Board />
108
132
  </ChessGame.Root>
@@ -207,6 +231,107 @@ Enables keyboard navigation through the game history.
207
231
  </ChessGame.Root>
208
232
  ```
209
233
 
234
+ ### ChessGame.Clock
235
+
236
+ Integrated chess clock components. When `timeControl` is provided to `ChessGame.Root`, these components become available to display and control the clock.
237
+
238
+ The clock automatically switches when moves are made on the board (can be disabled with `autoSwitchOnMove={false}`).
239
+
240
+ **Note:** These are wrapper components that use the clock state from `ChessGame.Root`. No need for a separate `ChessClock.Root` provider.
241
+
242
+ #### Available Components
243
+
244
+ | Component | Description |
245
+ | --------------------------- | -------------------------------------------- |
246
+ | `ChessGame.Clock.Display` | Displays the current time for a player |
247
+ | `ChessGame.Clock.Switch` | Button to manually switch the active clock |
248
+ | `ChessGame.Clock.PlayPause` | Button to start, pause, and resume the clock |
249
+ | `ChessGame.Clock.Reset` | Button to reset the clock |
250
+
251
+ #### ChessGame.Clock.Display
252
+
253
+ Displays the current time for a player.
254
+
255
+ ```tsx
256
+ <ChessGame.Clock.Display
257
+ color="white"
258
+ format="auto"
259
+ style={{ fontFamily: "monospace", fontSize: "24px" }}
260
+ />
261
+ ```
262
+
263
+ **Props:**
264
+
265
+ | Name | Type | Default | Description |
266
+ | ------------ | ------------------------------------------- | -------- | ---------------------------------- |
267
+ | `color` | `"white" \| "black"` | - | Player color to display (required) |
268
+ | `format` | `"auto" \| "mm:ss" \| "ss.d" \| "hh:mm:ss"` | `"auto"` | Time format |
269
+ | `formatTime` | `(milliseconds: number) => string` | - | Custom time formatting function |
270
+ | `...` | `HTMLAttributes<HTMLDivElement>` | - | All standard HTML div attributes |
271
+
272
+ #### ChessGame.Clock.Switch
273
+
274
+ A button that manually switches the active player's clock.
275
+
276
+ ```tsx
277
+ <ChessGame.Clock.Switch>Switch Turn</ChessGame.Clock.Switch>
278
+ ```
279
+
280
+ **Props:**
281
+
282
+ | Name | Type | Default | Description |
283
+ | --------- | ----------------------------------------- | ------- | -------------------------------------- |
284
+ | `asChild` | `boolean` | `false` | Render as child element (slot pattern) |
285
+ | `...` | `ButtonHTMLAttributes<HTMLButtonElement>` | - | All standard HTML button attributes |
286
+
287
+ #### ChessGame.Clock.PlayPause
288
+
289
+ A button to start, pause, and resume the clock.
290
+
291
+ ```tsx
292
+ <ChessGame.Clock.PlayPause
293
+ startContent="Start Game"
294
+ pauseContent="Pause"
295
+ resumeContent="Resume"
296
+ />
297
+ ```
298
+
299
+ **Props:**
300
+
301
+ | Name | Type | Default | Description |
302
+ | ----------------- | ----------------------------------------- | ------------- | -------------------------------------- |
303
+ | `startContent` | `ReactNode` | `"Start"` | Content shown when clock is idle |
304
+ | `pauseContent` | `ReactNode` | `"Pause"` | Content shown when clock is running |
305
+ | `resumeContent` | `ReactNode` | `"Resume"` | Content shown when clock is paused |
306
+ | `delayedContent` | `ReactNode` | `"Start"` | Content shown when clock is delayed |
307
+ | `finishedContent` | `ReactNode` | `"Game Over"` | Content shown when clock is finished |
308
+ | `asChild` | `boolean` | `false` | Render as child element (slot pattern) |
309
+ | `...` | `ButtonHTMLAttributes<HTMLButtonElement>` | - | All standard HTML button attributes |
310
+
311
+ #### ChessGame.Clock.Reset
312
+
313
+ A button that resets the clock.
314
+
315
+ ```tsx
316
+ // Reset with same time control
317
+ <ChessGame.Clock.Reset>Reset</ChessGame.Clock.Reset>
318
+
319
+ // Reset with new time control
320
+ <ChessGame.Clock.Reset timeControl="10+5">
321
+ Change to 10+5
322
+ </ChessGame.Clock.Reset>
323
+ ```
324
+
325
+ **Props:**
326
+
327
+ | Name | Type | Description |
328
+ | ------------- | ----------------------------------------- | --------------------------------------------- |
329
+ | `timeControl` | `TimeControlInput` | New time control to apply on reset (optional) |
330
+ | `asChild` | `boolean` | Render as child element (slot pattern) |
331
+ | `...` | `ButtonHTMLAttributes<HTMLButtonElement>` | All standard HTML button attributes |
332
+
333
+ For more details on time control formats, timing methods, and clock start modes, see the [`@react-chess-tools/react-chess-clock` documentation](https://www.npmjs.com/package/@react-chess-tools/react-chess-clock).
334
+
210
335
  ## Hooks
211
336
 
212
337
  ### useChessGameContext
@@ -217,13 +342,14 @@ Access the chess game context from any child component.
217
342
  import { useChessGameContext } from "@react-chess-tools/react-chess-game";
218
343
 
219
344
  function GameStatus() {
220
- const { currentFen, info, methods } = useChessGameContext();
345
+ const { currentFen, info, methods, clock } = useChessGameContext();
221
346
 
222
347
  return (
223
348
  <div>
224
349
  <p>Turn: {info.turn === "w" ? "White" : "Black"}</p>
225
350
  {info.isCheck && <p>Check!</p>}
226
351
  {info.isCheckmate && <p>Checkmate!</p>}
352
+ {clock && <p>White: {clock.times.white}ms</p>}
227
353
  <button onClick={() => methods.flipBoard()}>Flip Board</button>
228
354
  </div>
229
355
  );
@@ -232,16 +358,17 @@ function GameStatus() {
232
358
 
233
359
  #### Return Values
234
360
 
235
- | Name | Type | Description |
236
- | ------------------ | ------------ | ----------------------------------- |
237
- | `game` | `Chess` | The underlying chess.js instance |
238
- | `orientation` | `"w" \| "b"` | Current board orientation |
239
- | `currentFen` | `string` | Current FEN string |
240
- | `currentPosition` | `string` | Current position in game history |
241
- | `currentMoveIndex` | `number` | Index of current move in history |
242
- | `isLatestMove` | `boolean` | Whether viewing the latest position |
243
- | `methods` | `Methods` | Methods to interact with the game |
244
- | `info` | `Info` | Game state information |
361
+ | Name | Type | Description |
362
+ | ------------------ | ----------------------------- | ------------------------------------- |
363
+ | `game` | `Chess` | The underlying chess.js instance |
364
+ | `orientation` | `"w" \| "b"` | Current board orientation |
365
+ | `currentFen` | `string` | Current FEN string |
366
+ | `currentPosition` | `string` | Current position in game history |
367
+ | `currentMoveIndex` | `number` | Index of current move in history |
368
+ | `isLatestMove` | `boolean` | Whether viewing the latest position |
369
+ | `methods` | `Methods` | Methods to interact with the game |
370
+ | `info` | `Info` | Game state information |
371
+ | `clock` | `UseChessClockReturn \| null` | Clock state (if timeControl provided) |
245
372
 
246
373
  #### Methods
247
374
 
@@ -294,6 +421,24 @@ function FullFeaturedGame() {
294
421
  }
295
422
  ```
296
423
 
424
+ ### Game with Chess Clock
425
+
426
+ ```tsx
427
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
428
+
429
+ function GameWithClock() {
430
+ return (
431
+ <ChessGame.Root timeControl={{ time: "5+3" }}>
432
+ <ChessGame.Clock.Display color="white" />
433
+ <ChessGame.Board />
434
+ <ChessGame.Clock.Display color="black" />
435
+ <ChessGame.Clock.PlayPause />
436
+ <ChessGame.Clock.Reset>Reset</ChessGame.Clock.Reset>
437
+ </ChessGame.Root>
438
+ );
439
+ }
440
+ ```
441
+
297
442
  ### Custom Starting Position
298
443
 
299
444
  ```tsx
@@ -390,6 +535,30 @@ function GameWithStatus() {
390
535
  }
391
536
  ```
392
537
 
538
+ ### Using ChessClock Directly
539
+
540
+ You can also use `ChessClock` components directly alongside `ChessGame`:
541
+
542
+ ```tsx
543
+ import { ChessGame, ChessClock } from "@react-chess-tools/react-chess-game";
544
+
545
+ function GameWithSeparateClock() {
546
+ return (
547
+ <div>
548
+ <ChessClock.Root timeControl={{ time: "10+0" }}>
549
+ <ChessClock.Display color="white" />
550
+ <ChessClock.Display color="black" />
551
+ <ChessClock.PlayPause />
552
+ </ChessClock.Root>
553
+
554
+ <ChessGame.Root>
555
+ <ChessGame.Board />
556
+ </ChessGame.Root>
557
+ </div>
558
+ );
559
+ }
560
+ ```
561
+
393
562
  ## License
394
563
 
395
564
  This project is [MIT](https://opensource.org/licenses/MIT) licensed.
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ChessClock: () => import_react_chess_clock3.ChessClock,
33
34
  ChessGame: () => ChessGame,
34
35
  ChessGameThemeContext: () => ChessGameThemeContext,
35
36
  chessComTheme: () => chessComTheme,
@@ -155,9 +156,12 @@ var getCurrentFen = (fen, game, currentMoveIndex) => {
155
156
  };
156
157
 
157
158
  // src/hooks/useChessGame.ts
159
+ var import_react_chess_clock = require("@react-chess-tools/react-chess-clock");
158
160
  var useChessGame = ({
159
161
  fen,
160
- orientation: initialOrientation
162
+ orientation: initialOrientation,
163
+ timeControl,
164
+ autoSwitchOnMove = true
161
165
  } = {}) => {
162
166
  const [game, setGame] = import_react.default.useState(() => {
163
167
  try {
@@ -180,22 +184,19 @@ var useChessGame = ({
180
184
  );
181
185
  const [currentMoveIndex, setCurrentMoveIndex] = import_react.default.useState(-1);
182
186
  const history = import_react.default.useMemo(() => game.history(), [game]);
183
- const isLatestMove = import_react.default.useMemo(
184
- () => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,
185
- [currentMoveIndex, history.length]
186
- );
187
+ const isLatestMove = currentMoveIndex === history.length - 1 || currentMoveIndex === -1;
187
188
  const info = import_react.default.useMemo(
188
189
  () => getGameInfo(game, orientation),
189
190
  [game, orientation]
190
191
  );
191
192
  const currentFen = import_react.default.useMemo(
192
193
  () => getCurrentFen(fen, game, currentMoveIndex),
193
- [game, currentMoveIndex]
194
- );
195
- const currentPosition = import_react.default.useMemo(
196
- () => game.history()[currentMoveIndex],
197
- [game, currentMoveIndex]
194
+ [fen, game, currentMoveIndex]
198
195
  );
196
+ const currentPosition = game.history()[currentMoveIndex];
197
+ const clockState = (0, import_react_chess_clock.useOptionalChessClock)(timeControl);
198
+ const clockStateRef = (0, import_react.useRef)(clockState);
199
+ clockStateRef.current = clockState;
199
200
  const setPosition = import_react.default.useCallback((fen2, orientation2) => {
200
201
  try {
201
202
  const newGame = new import_chess2.Chess();
@@ -212,17 +213,30 @@ var useChessGame = ({
212
213
  if (!isLatestMove) {
213
214
  return false;
214
215
  }
216
+ const clock = clockStateRef.current;
217
+ if (clock && clock.timeout !== null) {
218
+ return false;
219
+ }
215
220
  try {
216
221
  const copy = cloneGame(game);
217
222
  copy.move(move);
218
223
  setGame(copy);
219
224
  setCurrentMoveIndex(copy.history().length - 1);
225
+ if (clock && clock.status === "idle") {
226
+ clock.methods.start();
227
+ }
228
+ if (clock && clock.status === "running" && copy.isGameOver()) {
229
+ clock.methods.pause();
230
+ }
231
+ if (autoSwitchOnMove && clock && clock.status !== "finished") {
232
+ clock.methods.switch();
233
+ }
220
234
  return true;
221
235
  } catch (e) {
222
236
  return false;
223
237
  }
224
238
  },
225
- [isLatestMove, game]
239
+ [isLatestMove, game, autoSwitchOnMove]
226
240
  );
227
241
  const flipBoard = import_react.default.useCallback(() => {
228
242
  setOrientation((orientation2) => orientation2 === "w" ? "b" : "w");
@@ -277,7 +291,8 @@ var useChessGame = ({
277
291
  currentMoveIndex,
278
292
  isLatestMove,
279
293
  info,
280
- methods
294
+ methods,
295
+ clock: clockState
281
296
  };
282
297
  };
283
298
 
@@ -347,9 +362,16 @@ var Root = ({
347
362
  fen,
348
363
  orientation,
349
364
  theme,
365
+ timeControl,
366
+ autoSwitchOnMove,
350
367
  children
351
368
  }) => {
352
- const context = useChessGame({ fen, orientation });
369
+ const context = useChessGame({
370
+ fen,
371
+ orientation,
372
+ timeControl,
373
+ autoSwitchOnMove
374
+ });
353
375
  const mergedTheme = import_react4.default.useMemo(() => mergeTheme(theme), [theme]);
354
376
  return /* @__PURE__ */ import_react4.default.createElement(ChessGameContext.Provider, { value: context }, /* @__PURE__ */ import_react4.default.createElement(ThemeProvider, { theme: mergedTheme }, children));
355
377
  };
@@ -719,14 +741,62 @@ var KeyboardControls2 = ({
719
741
  };
720
742
  KeyboardControls2.displayName = "ChessGame.KeyboardControls";
721
743
 
744
+ // src/components/ChessGame/Clock/index.tsx
745
+ var import_react9 = __toESM(require("react"), 1);
746
+ var import_react_chess_clock2 = require("@react-chess-tools/react-chess-clock");
747
+ var Display = import_react9.default.forwardRef(
748
+ ({ color, ...rest }, ref) => {
749
+ const { clock } = useChessGameContext();
750
+ if (!clock) {
751
+ return null;
752
+ }
753
+ return /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.Display, { ref, color, ...rest }));
754
+ }
755
+ );
756
+ Display.displayName = "ChessGame.Clock.Display";
757
+ var Switch = import_react9.default.forwardRef(({ children, ...rest }, ref) => {
758
+ const { clock } = useChessGameContext();
759
+ if (!clock) {
760
+ return null;
761
+ }
762
+ return /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.Switch, { ref, ...rest }, children));
763
+ });
764
+ Switch.displayName = "ChessGame.Clock.Switch";
765
+ var PlayPause = import_react9.default.forwardRef(({ children, ...rest }, ref) => {
766
+ const { clock } = useChessGameContext();
767
+ if (!clock) {
768
+ return null;
769
+ }
770
+ return /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.PlayPause, { ref, ...rest }, children));
771
+ });
772
+ PlayPause.displayName = "ChessGame.Clock.PlayPause";
773
+ var Reset = import_react9.default.forwardRef(({ children, ...rest }, ref) => {
774
+ const { clock } = useChessGameContext();
775
+ if (!clock) {
776
+ return null;
777
+ }
778
+ return /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ import_react9.default.createElement(import_react_chess_clock2.Reset, { ref, ...rest }, children));
779
+ });
780
+ Reset.displayName = "ChessGame.Clock.Reset";
781
+ var Clock = {
782
+ Display,
783
+ Switch,
784
+ PlayPause,
785
+ Reset
786
+ };
787
+
722
788
  // src/components/ChessGame/index.ts
723
789
  var ChessGame = {
724
790
  Root,
725
791
  Board,
726
792
  Sounds,
727
- KeyboardControls: KeyboardControls2
793
+ KeyboardControls: KeyboardControls2,
794
+ Clock
728
795
  };
729
796
 
797
+ // src/index.ts
798
+ var import_react_chess_clock3 = require("@react-chess-tools/react-chess-clock");
799
+
730
800
  // src/theme/presets.ts
731
801
  var lichessTheme = {
732
802
  board: {
@@ -769,6 +839,7 @@ var themes = {
769
839
  };
770
840
  // Annotate the CommonJS export names for ESM import in node:
771
841
  0 && (module.exports = {
842
+ ChessClock,
772
843
  ChessGame,
773
844
  ChessGameThemeContext,
774
845
  chessComTheme,