@react-chess-tools/react-chess-game 1.0.0 → 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/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import React4 from "react";
3
3
 
4
4
  // src/hooks/useChessGame.ts
5
- import React, { useEffect } from "react";
5
+ import React, { useEffect, useRef } from "react";
6
6
  import { Chess as Chess2 } from "chess.js";
7
7
 
8
8
  // src/utils/chess.ts
@@ -108,9 +108,12 @@ var getCurrentFen = (fen, game, currentMoveIndex) => {
108
108
  };
109
109
 
110
110
  // src/hooks/useChessGame.ts
111
+ import { useOptionalChessClock } from "@react-chess-tools/react-chess-clock";
111
112
  var useChessGame = ({
112
113
  fen,
113
- orientation: initialOrientation
114
+ orientation: initialOrientation,
115
+ timeControl,
116
+ autoSwitchOnMove = true
114
117
  } = {}) => {
115
118
  const [game, setGame] = React.useState(() => {
116
119
  try {
@@ -133,22 +136,19 @@ var useChessGame = ({
133
136
  );
134
137
  const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);
135
138
  const history = React.useMemo(() => game.history(), [game]);
136
- const isLatestMove = React.useMemo(
137
- () => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,
138
- [currentMoveIndex, history.length]
139
- );
139
+ const isLatestMove = currentMoveIndex === history.length - 1 || currentMoveIndex === -1;
140
140
  const info = React.useMemo(
141
141
  () => getGameInfo(game, orientation),
142
142
  [game, orientation]
143
143
  );
144
144
  const currentFen = React.useMemo(
145
145
  () => getCurrentFen(fen, game, currentMoveIndex),
146
- [game, currentMoveIndex]
147
- );
148
- const currentPosition = React.useMemo(
149
- () => game.history()[currentMoveIndex],
150
- [game, currentMoveIndex]
146
+ [fen, game, currentMoveIndex]
151
147
  );
148
+ const currentPosition = game.history()[currentMoveIndex];
149
+ const clockState = useOptionalChessClock(timeControl);
150
+ const clockStateRef = useRef(clockState);
151
+ clockStateRef.current = clockState;
152
152
  const setPosition = React.useCallback((fen2, orientation2) => {
153
153
  try {
154
154
  const newGame = new Chess2();
@@ -165,17 +165,30 @@ var useChessGame = ({
165
165
  if (!isLatestMove) {
166
166
  return false;
167
167
  }
168
+ const clock = clockStateRef.current;
169
+ if (clock && clock.timeout !== null) {
170
+ return false;
171
+ }
168
172
  try {
169
173
  const copy = cloneGame(game);
170
174
  copy.move(move);
171
175
  setGame(copy);
172
176
  setCurrentMoveIndex(copy.history().length - 1);
177
+ if (clock && clock.status === "idle") {
178
+ clock.methods.start();
179
+ }
180
+ if (clock && clock.status === "running" && copy.isGameOver()) {
181
+ clock.methods.pause();
182
+ }
183
+ if (autoSwitchOnMove && clock && clock.status !== "finished") {
184
+ clock.methods.switch();
185
+ }
173
186
  return true;
174
187
  } catch (e) {
175
188
  return false;
176
189
  }
177
190
  },
178
- [isLatestMove, game]
191
+ [isLatestMove, game, autoSwitchOnMove]
179
192
  );
180
193
  const flipBoard = React.useCallback(() => {
181
194
  setOrientation((orientation2) => orientation2 === "w" ? "b" : "w");
@@ -230,7 +243,8 @@ var useChessGame = ({
230
243
  currentMoveIndex,
231
244
  isLatestMove,
232
245
  info,
233
- methods
246
+ methods,
247
+ clock: clockState
234
248
  };
235
249
  };
236
250
 
@@ -300,12 +314,20 @@ var Root = ({
300
314
  fen,
301
315
  orientation,
302
316
  theme,
317
+ timeControl,
318
+ autoSwitchOnMove,
303
319
  children
304
320
  }) => {
305
- const context = useChessGame({ fen, orientation });
321
+ const context = useChessGame({
322
+ fen,
323
+ orientation,
324
+ timeControl,
325
+ autoSwitchOnMove
326
+ });
306
327
  const mergedTheme = React4.useMemo(() => mergeTheme(theme), [theme]);
307
328
  return /* @__PURE__ */ React4.createElement(ChessGameContext.Provider, { value: context }, /* @__PURE__ */ React4.createElement(ThemeProvider, { theme: mergedTheme }, children));
308
329
  };
330
+ Root.displayName = "ChessGame.Root";
309
331
 
310
332
  // src/components/ChessGame/parts/Board.tsx
311
333
  import React5 from "react";
@@ -375,192 +397,199 @@ var deepMergeChessboardOptions = (baseOptions, customOptions) => {
375
397
  };
376
398
 
377
399
  // src/components/ChessGame/parts/Board.tsx
378
- var Board = ({ options = {} }) => {
379
- var _a, _b, _c;
380
- const gameContext = useChessGameContext();
381
- const theme = useChessGameTheme();
382
- if (!gameContext) {
383
- throw new Error("ChessGameContext not found");
384
- }
385
- const {
386
- game,
387
- currentFen,
388
- orientation,
389
- info,
390
- isLatestMove,
391
- methods: { makeMove }
392
- } = gameContext;
393
- const { turn, isGameOver } = info;
394
- const [activeSquare, setActiveSquare] = React5.useState(null);
395
- const [promotionMove, setPromotionMove] = React5.useState(null);
396
- const onSquareClick = (square) => {
397
- if (isGameOver) {
398
- return;
400
+ var Board = React5.forwardRef(
401
+ ({ options = {}, className, style: userStyle, ...rest }, ref) => {
402
+ var _a, _b, _c;
403
+ const gameContext = useChessGameContext();
404
+ const theme = useChessGameTheme();
405
+ if (!gameContext) {
406
+ throw new Error("ChessGameContext not found");
399
407
  }
400
- if (activeSquare === null) {
401
- const squadreInfo = game.get(square);
402
- if (squadreInfo && squadreInfo.color === turn) {
403
- return setActiveSquare(square);
408
+ const {
409
+ game,
410
+ currentFen,
411
+ orientation,
412
+ info,
413
+ isLatestMove,
414
+ methods: { makeMove }
415
+ } = gameContext;
416
+ const { turn, isGameOver } = info;
417
+ const [activeSquare, setActiveSquare] = React5.useState(null);
418
+ const [promotionMove, setPromotionMove] = React5.useState(null);
419
+ const onSquareClick = (square) => {
420
+ if (isGameOver) {
421
+ return;
404
422
  }
405
- return;
406
- }
407
- if (!isLegalMove(game, {
408
- from: activeSquare,
409
- to: square,
410
- promotion: "q"
411
- })) {
412
- return setActiveSquare(null);
413
- }
414
- if (requiresPromotion(game, {
415
- from: activeSquare,
416
- to: square,
417
- promotion: "q"
418
- })) {
419
- return setPromotionMove({
423
+ if (activeSquare === null) {
424
+ const squadreInfo = game.get(square);
425
+ if (squadreInfo && squadreInfo.color === turn) {
426
+ return setActiveSquare(square);
427
+ }
428
+ return;
429
+ }
430
+ if (!isLegalMove(game, {
420
431
  from: activeSquare,
421
- to: square
422
- });
423
- }
424
- setActiveSquare(null);
425
- makeMove({
426
- from: activeSquare,
427
- to: square
428
- });
429
- };
430
- const onPromotionPieceSelect = (piece) => {
431
- if ((promotionMove == null ? void 0 : promotionMove.from) && (promotionMove == null ? void 0 : promotionMove.to)) {
432
+ to: square,
433
+ promotion: "q"
434
+ })) {
435
+ return setActiveSquare(null);
436
+ }
437
+ if (requiresPromotion(game, {
438
+ from: activeSquare,
439
+ to: square,
440
+ promotion: "q"
441
+ })) {
442
+ return setPromotionMove({
443
+ from: activeSquare,
444
+ to: square
445
+ });
446
+ }
447
+ setActiveSquare(null);
432
448
  makeMove({
433
- from: promotionMove.from,
434
- to: promotionMove.to,
435
- promotion: piece.toLowerCase()
449
+ from: activeSquare,
450
+ to: square
436
451
  });
437
- setPromotionMove(null);
438
- }
439
- };
440
- const onSquareRightClick = () => {
441
- setActiveSquare(null);
442
- setPromotionMove(null);
443
- };
444
- const squareWidth = React5.useMemo(() => {
445
- var _a2;
446
- if (typeof document === "undefined") return 80;
447
- const squareElement = document.querySelector(`[data-square]`);
448
- return ((_a2 = squareElement == null ? void 0 : squareElement.getBoundingClientRect()) == null ? void 0 : _a2.width) ?? 80;
449
- }, [promotionMove]);
450
- const promotionSquareLeft = React5.useMemo(() => {
451
- var _a2;
452
- if (!(promotionMove == null ? void 0 : promotionMove.to)) return 0;
453
- const column = ((_a2 = promotionMove.to.match(/^[a-h]/)) == null ? void 0 : _a2[0]) ?? "a";
454
- return squareWidth * chessColumnToColumnIndex(
455
- column,
456
- 8,
457
- orientation === "b" ? "black" : "white"
458
- );
459
- }, [promotionMove, squareWidth, orientation]);
460
- const baseOptions = {
461
- squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
462
- boardOrientation: orientation === "b" ? "black" : "white",
463
- position: currentFen,
464
- showNotation: true,
465
- showAnimations: isLatestMove,
466
- lightSquareStyle: theme.board.lightSquare,
467
- darkSquareStyle: theme.board.darkSquare,
468
- canDragPiece: ({ piece }) => {
469
- if (isGameOver) return false;
470
- return piece.pieceType[0] === turn;
471
- },
472
- dropSquareStyle: theme.state.dropSquare,
473
- onPieceDrag: ({ piece, square }) => {
474
- if (piece.pieceType[0] === turn) {
475
- setActiveSquare(square);
452
+ };
453
+ const onPromotionPieceSelect = (piece) => {
454
+ if ((promotionMove == null ? void 0 : promotionMove.from) && (promotionMove == null ? void 0 : promotionMove.to)) {
455
+ makeMove({
456
+ from: promotionMove.from,
457
+ to: promotionMove.to,
458
+ promotion: piece.toLowerCase()
459
+ });
460
+ setPromotionMove(null);
476
461
  }
477
- },
478
- onPieceDrop: ({ sourceSquare, targetSquare }) => {
462
+ };
463
+ const onSquareRightClick = () => {
479
464
  setActiveSquare(null);
480
- const moveData = {
481
- from: sourceSquare,
482
- to: targetSquare
483
- };
484
- if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
485
- setPromotionMove(moveData);
486
- return false;
487
- }
488
- return makeMove(moveData);
489
- },
490
- onSquareClick: ({ square }) => {
491
- if (square.match(/^[a-h][1-8]$/)) {
492
- onSquareClick(square);
493
- }
494
- },
495
- onSquareRightClick,
496
- allowDrawingArrows: true,
497
- animationDurationInMs: game.history().length === 0 ? 0 : 300
498
- };
499
- const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
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(
501
- "div",
502
- {
503
- onClick: () => setPromotionMove(null),
504
- onContextMenu: (e) => {
505
- e.preventDefault();
506
- setPromotionMove(null);
465
+ setPromotionMove(null);
466
+ };
467
+ const squareWidth = React5.useMemo(() => {
468
+ var _a2;
469
+ if (typeof document === "undefined") return 80;
470
+ const squareElement = document.querySelector(`[data-square]`);
471
+ return ((_a2 = squareElement == null ? void 0 : squareElement.getBoundingClientRect()) == null ? void 0 : _a2.width) ?? 80;
472
+ }, [promotionMove]);
473
+ const promotionSquareLeft = React5.useMemo(() => {
474
+ var _a2;
475
+ if (!(promotionMove == null ? void 0 : promotionMove.to)) return 0;
476
+ const column = ((_a2 = promotionMove.to.match(/^[a-h]/)) == null ? void 0 : _a2[0]) ?? "a";
477
+ return squareWidth * chessColumnToColumnIndex(
478
+ column,
479
+ 8,
480
+ orientation === "b" ? "black" : "white"
481
+ );
482
+ }, [promotionMove, squareWidth, orientation]);
483
+ const baseOptions = {
484
+ squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
485
+ boardOrientation: orientation === "b" ? "black" : "white",
486
+ position: currentFen,
487
+ showNotation: true,
488
+ showAnimations: isLatestMove,
489
+ lightSquareStyle: theme.board.lightSquare,
490
+ darkSquareStyle: theme.board.darkSquare,
491
+ canDragPiece: ({ piece }) => {
492
+ if (isGameOver) return false;
493
+ return piece.pieceType[0] === turn;
507
494
  },
508
- style: {
509
- position: "absolute",
510
- top: 0,
511
- left: 0,
512
- right: 0,
513
- bottom: 0,
514
- backgroundColor: "rgba(0, 0, 0, 0.1)",
515
- zIndex: 1e3
516
- }
517
- }
518
- ), /* @__PURE__ */ React5.createElement(
519
- "div",
520
- {
521
- style: {
522
- position: "absolute",
523
- top: ((_b = (_a = promotionMove.to) == null ? void 0 : _a[1]) == null ? void 0 : _b.includes("8")) ? 0 : "auto",
524
- bottom: ((_c = promotionMove.to) == null ? void 0 : _c[1].includes("1")) ? 0 : "auto",
525
- left: promotionSquareLeft,
526
- backgroundColor: "white",
527
- width: squareWidth,
528
- zIndex: 1001,
529
- display: "flex",
530
- flexDirection: "column",
531
- boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.5)"
532
- }
533
- },
534
- ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ React5.createElement(
535
- "button",
495
+ dropSquareStyle: theme.state.dropSquare,
496
+ onPieceDrag: ({ piece, square }) => {
497
+ if (piece.pieceType[0] === turn) {
498
+ setActiveSquare(square);
499
+ }
500
+ },
501
+ onPieceDrop: ({ sourceSquare, targetSquare }) => {
502
+ setActiveSquare(null);
503
+ const moveData = {
504
+ from: sourceSquare,
505
+ to: targetSquare
506
+ };
507
+ if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
508
+ setPromotionMove(moveData);
509
+ return false;
510
+ }
511
+ return makeMove(moveData);
512
+ },
513
+ onSquareClick: ({ square }) => {
514
+ if (square.match(/^[a-h][1-8]$/)) {
515
+ onSquareClick(square);
516
+ }
517
+ },
518
+ onSquareRightClick,
519
+ allowDrawingArrows: true,
520
+ animationDurationInMs: game.history().length === 0 ? 0 : 300
521
+ };
522
+ const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
523
+ const mergedStyle = {
524
+ ...userStyle,
525
+ position: "relative"
526
+ };
527
+ return /* @__PURE__ */ React5.createElement("div", { ref, className, style: mergedStyle, ...rest }, /* @__PURE__ */ React5.createElement(Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(
528
+ "div",
536
529
  {
537
- key: piece,
538
- onClick: () => onPromotionPieceSelect(piece),
530
+ onClick: () => setPromotionMove(null),
539
531
  onContextMenu: (e) => {
540
532
  e.preventDefault();
533
+ setPromotionMove(null);
541
534
  },
542
535
  style: {
543
- width: "100%",
544
- aspectRatio: "1",
536
+ position: "absolute",
537
+ top: 0,
538
+ left: 0,
539
+ right: 0,
540
+ bottom: 0,
541
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
542
+ zIndex: 1e3
543
+ }
544
+ }
545
+ ), /* @__PURE__ */ React5.createElement(
546
+ "div",
547
+ {
548
+ style: {
549
+ position: "absolute",
550
+ top: ((_b = (_a = promotionMove.to) == null ? void 0 : _a[1]) == null ? void 0 : _b.includes("8")) ? 0 : "auto",
551
+ bottom: ((_c = promotionMove.to) == null ? void 0 : _c[1].includes("1")) ? 0 : "auto",
552
+ left: promotionSquareLeft,
553
+ backgroundColor: "white",
554
+ width: squareWidth,
555
+ zIndex: 1001,
545
556
  display: "flex",
546
- alignItems: "center",
547
- justifyContent: "center",
548
- padding: 0,
549
- border: "none",
550
- cursor: "pointer",
551
- backgroundColor: "white"
552
- },
553
- onMouseEnter: (e) => {
554
- e.currentTarget.style.backgroundColor = "#f0f0f0";
555
- },
556
- onMouseLeave: (e) => {
557
- e.currentTarget.style.backgroundColor = "white";
557
+ flexDirection: "column",
558
+ boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.5)"
558
559
  }
559
560
  },
560
- defaultPieces[`${turn}${piece.toUpperCase()}`]()
561
- ))
562
- )));
563
- };
561
+ ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ React5.createElement(
562
+ "button",
563
+ {
564
+ key: piece,
565
+ onClick: () => onPromotionPieceSelect(piece),
566
+ onContextMenu: (e) => {
567
+ e.preventDefault();
568
+ },
569
+ style: {
570
+ width: "100%",
571
+ aspectRatio: "1",
572
+ display: "flex",
573
+ alignItems: "center",
574
+ justifyContent: "center",
575
+ padding: 0,
576
+ border: "none",
577
+ cursor: "pointer",
578
+ backgroundColor: "white"
579
+ },
580
+ onMouseEnter: (e) => {
581
+ e.currentTarget.style.backgroundColor = "#f0f0f0";
582
+ },
583
+ onMouseLeave: (e) => {
584
+ e.currentTarget.style.backgroundColor = "white";
585
+ }
586
+ },
587
+ defaultPieces[`${turn}${piece.toUpperCase()}`]()
588
+ ))
589
+ )));
590
+ }
591
+ );
592
+ Board.displayName = "ChessGame.Board";
564
593
 
565
594
  // src/components/ChessGame/parts/Sounds.tsx
566
595
  import { useMemo } from "react";
@@ -622,6 +651,7 @@ var Sounds = ({ sounds }) => {
622
651
  useBoardSounds(customSoundsAudios);
623
652
  return null;
624
653
  };
654
+ Sounds.displayName = "ChessGame.Sounds";
625
655
 
626
656
  // src/hooks/useKeyboardControls.ts
627
657
  import { useEffect as useEffect3 } from "react";
@@ -665,15 +695,70 @@ var KeyboardControls2 = ({
665
695
  useKeyboardControls(keyboardControls);
666
696
  return null;
667
697
  };
698
+ KeyboardControls2.displayName = "ChessGame.KeyboardControls";
699
+
700
+ // src/components/ChessGame/Clock/index.tsx
701
+ import React6 from "react";
702
+ import {
703
+ Display as ClockDisplay,
704
+ Switch as ClockSwitch,
705
+ PlayPause as ClockPlayPause,
706
+ Reset as ClockReset,
707
+ ChessClockContext
708
+ } from "@react-chess-tools/react-chess-clock";
709
+ var Display = React6.forwardRef(
710
+ ({ color, ...rest }, ref) => {
711
+ const { clock } = useChessGameContext();
712
+ if (!clock) {
713
+ return null;
714
+ }
715
+ return /* @__PURE__ */ React6.createElement(ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ React6.createElement(ClockDisplay, { ref, color, ...rest }));
716
+ }
717
+ );
718
+ Display.displayName = "ChessGame.Clock.Display";
719
+ var Switch = React6.forwardRef(({ children, ...rest }, ref) => {
720
+ const { clock } = useChessGameContext();
721
+ if (!clock) {
722
+ return null;
723
+ }
724
+ return /* @__PURE__ */ React6.createElement(ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ React6.createElement(ClockSwitch, { ref, ...rest }, children));
725
+ });
726
+ Switch.displayName = "ChessGame.Clock.Switch";
727
+ var PlayPause = React6.forwardRef(({ children, ...rest }, ref) => {
728
+ const { clock } = useChessGameContext();
729
+ if (!clock) {
730
+ return null;
731
+ }
732
+ return /* @__PURE__ */ React6.createElement(ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ React6.createElement(ClockPlayPause, { ref, ...rest }, children));
733
+ });
734
+ PlayPause.displayName = "ChessGame.Clock.PlayPause";
735
+ var Reset = React6.forwardRef(({ children, ...rest }, ref) => {
736
+ const { clock } = useChessGameContext();
737
+ if (!clock) {
738
+ return null;
739
+ }
740
+ return /* @__PURE__ */ React6.createElement(ChessClockContext.Provider, { value: clock }, /* @__PURE__ */ React6.createElement(ClockReset, { ref, ...rest }, children));
741
+ });
742
+ Reset.displayName = "ChessGame.Clock.Reset";
743
+ var Clock = {
744
+ Display,
745
+ Switch,
746
+ PlayPause,
747
+ Reset
748
+ };
668
749
 
669
750
  // src/components/ChessGame/index.ts
670
751
  var ChessGame = {
671
752
  Root,
672
753
  Board,
673
754
  Sounds,
674
- KeyboardControls: KeyboardControls2
755
+ KeyboardControls: KeyboardControls2,
756
+ Clock
675
757
  };
676
758
 
759
+ // src/index.ts
760
+ import { ChessClock } from "@react-chess-tools/react-chess-clock";
761
+
677
762
  // src/theme/presets.ts
678
763
  var lichessTheme = {
679
764
  board: {
@@ -715,6 +800,7 @@ var themes = {
715
800
  chessCom: chessComTheme
716
801
  };
717
802
  export {
803
+ ChessClock,
718
804
  ChessGame,
719
805
  ChessGameThemeContext,
720
806
  chessComTheme,