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

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,13 @@
1
1
  # @react-chess-tools/react-chess-game
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 93e1029: feat: add chess clock
8
+ - Updated dependencies [93e1029]
9
+ - @react-chess-tools/react-chess-clock@1.0.1
10
+
3
11
  ## 1.0.1
4
12
 
5
13
  ### 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,108 @@ 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
+ <ChessGame.Clock.Reset>Reset</ChessGame.Clock.Reset>;
317
+
318
+ {
319
+ /* Reset with new time control */
320
+ }
321
+ <ChessGame.Clock.Reset timeControl="10+5">
322
+ Change to 10+5
323
+ </ChessGame.Clock.Reset>;
324
+ ```
325
+
326
+ **Props:**
327
+
328
+ | Name | Type | Description |
329
+ | ------------- | ----------------------------------------- | --------------------------------------------- |
330
+ | `timeControl` | `TimeControlInput` | New time control to apply on reset (optional) |
331
+ | `asChild` | `boolean` | Render as child element (slot pattern) |
332
+ | `...` | `ButtonHTMLAttributes<HTMLButtonElement>` | All standard HTML button attributes |
333
+
334
+ 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).
335
+
210
336
  ## Hooks
211
337
 
212
338
  ### useChessGameContext
@@ -217,13 +343,14 @@ Access the chess game context from any child component.
217
343
  import { useChessGameContext } from "@react-chess-tools/react-chess-game";
218
344
 
219
345
  function GameStatus() {
220
- const { currentFen, info, methods } = useChessGameContext();
346
+ const { currentFen, info, methods, clock } = useChessGameContext();
221
347
 
222
348
  return (
223
349
  <div>
224
350
  <p>Turn: {info.turn === "w" ? "White" : "Black"}</p>
225
351
  {info.isCheck && <p>Check!</p>}
226
352
  {info.isCheckmate && <p>Checkmate!</p>}
353
+ {clock && <p>White: {clock.times.white}ms</p>}
227
354
  <button onClick={() => methods.flipBoard()}>Flip Board</button>
228
355
  </div>
229
356
  );
@@ -232,16 +359,17 @@ function GameStatus() {
232
359
 
233
360
  #### Return Values
234
361
 
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 |
362
+ | Name | Type | Description |
363
+ | ------------------ | ----------------------------- | ------------------------------------- |
364
+ | `game` | `Chess` | The underlying chess.js instance |
365
+ | `orientation` | `"w" \| "b"` | Current board orientation |
366
+ | `currentFen` | `string` | Current FEN string |
367
+ | `currentPosition` | `string` | Current position in game history |
368
+ | `currentMoveIndex` | `number` | Index of current move in history |
369
+ | `isLatestMove` | `boolean` | Whether viewing the latest position |
370
+ | `methods` | `Methods` | Methods to interact with the game |
371
+ | `info` | `Info` | Game state information |
372
+ | `clock` | `UseChessClockReturn \| null` | Clock state (if timeControl provided) |
245
373
 
246
374
  #### Methods
247
375
 
@@ -294,6 +422,24 @@ function FullFeaturedGame() {
294
422
  }
295
423
  ```
296
424
 
425
+ ### Game with Chess Clock
426
+
427
+ ```tsx
428
+ import { ChessGame } from "@react-chess-tools/react-chess-game";
429
+
430
+ function GameWithClock() {
431
+ return (
432
+ <ChessGame.Root timeControl={{ time: "5+3" }}>
433
+ <ChessGame.Clock.Display color="white" />
434
+ <ChessGame.Board />
435
+ <ChessGame.Clock.Display color="black" />
436
+ <ChessGame.Clock.PlayPause />
437
+ <ChessGame.Clock.Reset>Reset</ChessGame.Clock.Reset>
438
+ </ChessGame.Root>
439
+ );
440
+ }
441
+ ```
442
+
297
443
  ### Custom Starting Position
298
444
 
299
445
  ```tsx
@@ -390,6 +536,30 @@ function GameWithStatus() {
390
536
  }
391
537
  ```
392
538
 
539
+ ### Using ChessClock Directly
540
+
541
+ You can also use `ChessClock` components directly alongside `ChessGame`:
542
+
543
+ ```tsx
544
+ import { ChessGame, ChessClock } from "@react-chess-tools/react-chess-game";
545
+
546
+ function GameWithSeparateClock() {
547
+ return (
548
+ <div>
549
+ <ChessClock.Root timeControl={{ time: "10+0" }}>
550
+ <ChessClock.Display color="white" />
551
+ <ChessClock.Display color="black" />
552
+ <ChessClock.PlayPause />
553
+ </ChessClock.Root>
554
+
555
+ <ChessGame.Root>
556
+ <ChessGame.Board />
557
+ </ChessGame.Root>
558
+ </div>
559
+ );
560
+ }
561
+ ```
562
+
393
563
  ## License
394
564
 
395
565
  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,