@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.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,12 +362,20 @@ 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
  };
378
+ Root.displayName = "ChessGame.Root";
356
379
 
357
380
  // src/components/ChessGame/parts/Board.tsx
358
381
  var import_react5 = __toESM(require("react"), 1);
@@ -418,192 +441,199 @@ var deepMergeChessboardOptions = (baseOptions, customOptions) => {
418
441
  };
419
442
 
420
443
  // src/components/ChessGame/parts/Board.tsx
421
- var Board = ({ options = {} }) => {
422
- var _a, _b, _c;
423
- const gameContext = useChessGameContext();
424
- const theme = useChessGameTheme();
425
- if (!gameContext) {
426
- throw new Error("ChessGameContext not found");
427
- }
428
- const {
429
- game,
430
- currentFen,
431
- orientation,
432
- info,
433
- isLatestMove,
434
- methods: { makeMove }
435
- } = gameContext;
436
- const { turn, isGameOver } = info;
437
- const [activeSquare, setActiveSquare] = import_react5.default.useState(null);
438
- const [promotionMove, setPromotionMove] = import_react5.default.useState(null);
439
- const onSquareClick = (square) => {
440
- if (isGameOver) {
441
- return;
444
+ var Board = import_react5.default.forwardRef(
445
+ ({ options = {}, className, style: userStyle, ...rest }, ref) => {
446
+ var _a, _b, _c;
447
+ const gameContext = useChessGameContext();
448
+ const theme = useChessGameTheme();
449
+ if (!gameContext) {
450
+ throw new Error("ChessGameContext not found");
442
451
  }
443
- if (activeSquare === null) {
444
- const squadreInfo = game.get(square);
445
- if (squadreInfo && squadreInfo.color === turn) {
446
- return setActiveSquare(square);
452
+ const {
453
+ game,
454
+ currentFen,
455
+ orientation,
456
+ info,
457
+ isLatestMove,
458
+ methods: { makeMove }
459
+ } = gameContext;
460
+ const { turn, isGameOver } = info;
461
+ const [activeSquare, setActiveSquare] = import_react5.default.useState(null);
462
+ const [promotionMove, setPromotionMove] = import_react5.default.useState(null);
463
+ const onSquareClick = (square) => {
464
+ if (isGameOver) {
465
+ return;
447
466
  }
448
- return;
449
- }
450
- if (!isLegalMove(game, {
451
- from: activeSquare,
452
- to: square,
453
- promotion: "q"
454
- })) {
455
- return setActiveSquare(null);
456
- }
457
- if (requiresPromotion(game, {
458
- from: activeSquare,
459
- to: square,
460
- promotion: "q"
461
- })) {
462
- return setPromotionMove({
467
+ if (activeSquare === null) {
468
+ const squadreInfo = game.get(square);
469
+ if (squadreInfo && squadreInfo.color === turn) {
470
+ return setActiveSquare(square);
471
+ }
472
+ return;
473
+ }
474
+ if (!isLegalMove(game, {
463
475
  from: activeSquare,
464
- to: square
465
- });
466
- }
467
- setActiveSquare(null);
468
- makeMove({
469
- from: activeSquare,
470
- to: square
471
- });
472
- };
473
- const onPromotionPieceSelect = (piece) => {
474
- if ((promotionMove == null ? void 0 : promotionMove.from) && (promotionMove == null ? void 0 : promotionMove.to)) {
476
+ to: square,
477
+ promotion: "q"
478
+ })) {
479
+ return setActiveSquare(null);
480
+ }
481
+ if (requiresPromotion(game, {
482
+ from: activeSquare,
483
+ to: square,
484
+ promotion: "q"
485
+ })) {
486
+ return setPromotionMove({
487
+ from: activeSquare,
488
+ to: square
489
+ });
490
+ }
491
+ setActiveSquare(null);
475
492
  makeMove({
476
- from: promotionMove.from,
477
- to: promotionMove.to,
478
- promotion: piece.toLowerCase()
493
+ from: activeSquare,
494
+ to: square
479
495
  });
480
- setPromotionMove(null);
481
- }
482
- };
483
- const onSquareRightClick = () => {
484
- setActiveSquare(null);
485
- setPromotionMove(null);
486
- };
487
- const squareWidth = import_react5.default.useMemo(() => {
488
- var _a2;
489
- if (typeof document === "undefined") return 80;
490
- const squareElement = document.querySelector(`[data-square]`);
491
- return ((_a2 = squareElement == null ? void 0 : squareElement.getBoundingClientRect()) == null ? void 0 : _a2.width) ?? 80;
492
- }, [promotionMove]);
493
- const promotionSquareLeft = import_react5.default.useMemo(() => {
494
- var _a2;
495
- if (!(promotionMove == null ? void 0 : promotionMove.to)) return 0;
496
- const column = ((_a2 = promotionMove.to.match(/^[a-h]/)) == null ? void 0 : _a2[0]) ?? "a";
497
- return squareWidth * (0, import_react_chessboard.chessColumnToColumnIndex)(
498
- column,
499
- 8,
500
- orientation === "b" ? "black" : "white"
501
- );
502
- }, [promotionMove, squareWidth, orientation]);
503
- const baseOptions = {
504
- squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
505
- boardOrientation: orientation === "b" ? "black" : "white",
506
- position: currentFen,
507
- showNotation: true,
508
- showAnimations: isLatestMove,
509
- lightSquareStyle: theme.board.lightSquare,
510
- darkSquareStyle: theme.board.darkSquare,
511
- canDragPiece: ({ piece }) => {
512
- if (isGameOver) return false;
513
- return piece.pieceType[0] === turn;
514
- },
515
- dropSquareStyle: theme.state.dropSquare,
516
- onPieceDrag: ({ piece, square }) => {
517
- if (piece.pieceType[0] === turn) {
518
- setActiveSquare(square);
496
+ };
497
+ const onPromotionPieceSelect = (piece) => {
498
+ if ((promotionMove == null ? void 0 : promotionMove.from) && (promotionMove == null ? void 0 : promotionMove.to)) {
499
+ makeMove({
500
+ from: promotionMove.from,
501
+ to: promotionMove.to,
502
+ promotion: piece.toLowerCase()
503
+ });
504
+ setPromotionMove(null);
519
505
  }
520
- },
521
- onPieceDrop: ({ sourceSquare, targetSquare }) => {
506
+ };
507
+ const onSquareRightClick = () => {
522
508
  setActiveSquare(null);
523
- const moveData = {
524
- from: sourceSquare,
525
- to: targetSquare
526
- };
527
- if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
528
- setPromotionMove(moveData);
529
- return false;
530
- }
531
- return makeMove(moveData);
532
- },
533
- onSquareClick: ({ square }) => {
534
- if (square.match(/^[a-h][1-8]$/)) {
535
- onSquareClick(square);
536
- }
537
- },
538
- onSquareRightClick,
539
- allowDrawingArrows: true,
540
- animationDurationInMs: game.history().length === 0 ? 0 : 300
541
- };
542
- const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
543
- return /* @__PURE__ */ import_react5.default.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ import_react5.default.createElement(import_react_chessboard.Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(
544
- "div",
545
- {
546
- onClick: () => setPromotionMove(null),
547
- onContextMenu: (e) => {
548
- e.preventDefault();
549
- setPromotionMove(null);
509
+ setPromotionMove(null);
510
+ };
511
+ const squareWidth = import_react5.default.useMemo(() => {
512
+ var _a2;
513
+ if (typeof document === "undefined") return 80;
514
+ const squareElement = document.querySelector(`[data-square]`);
515
+ return ((_a2 = squareElement == null ? void 0 : squareElement.getBoundingClientRect()) == null ? void 0 : _a2.width) ?? 80;
516
+ }, [promotionMove]);
517
+ const promotionSquareLeft = import_react5.default.useMemo(() => {
518
+ var _a2;
519
+ if (!(promotionMove == null ? void 0 : promotionMove.to)) return 0;
520
+ const column = ((_a2 = promotionMove.to.match(/^[a-h]/)) == null ? void 0 : _a2[0]) ?? "a";
521
+ return squareWidth * (0, import_react_chessboard.chessColumnToColumnIndex)(
522
+ column,
523
+ 8,
524
+ orientation === "b" ? "black" : "white"
525
+ );
526
+ }, [promotionMove, squareWidth, orientation]);
527
+ const baseOptions = {
528
+ squareStyles: getCustomSquareStyles(game, info, activeSquare, theme),
529
+ boardOrientation: orientation === "b" ? "black" : "white",
530
+ position: currentFen,
531
+ showNotation: true,
532
+ showAnimations: isLatestMove,
533
+ lightSquareStyle: theme.board.lightSquare,
534
+ darkSquareStyle: theme.board.darkSquare,
535
+ canDragPiece: ({ piece }) => {
536
+ if (isGameOver) return false;
537
+ return piece.pieceType[0] === turn;
550
538
  },
551
- style: {
552
- position: "absolute",
553
- top: 0,
554
- left: 0,
555
- right: 0,
556
- bottom: 0,
557
- backgroundColor: "rgba(0, 0, 0, 0.1)",
558
- zIndex: 1e3
559
- }
560
- }
561
- ), /* @__PURE__ */ import_react5.default.createElement(
562
- "div",
563
- {
564
- style: {
565
- position: "absolute",
566
- top: ((_b = (_a = promotionMove.to) == null ? void 0 : _a[1]) == null ? void 0 : _b.includes("8")) ? 0 : "auto",
567
- bottom: ((_c = promotionMove.to) == null ? void 0 : _c[1].includes("1")) ? 0 : "auto",
568
- left: promotionSquareLeft,
569
- backgroundColor: "white",
570
- width: squareWidth,
571
- zIndex: 1001,
572
- display: "flex",
573
- flexDirection: "column",
574
- boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.5)"
575
- }
576
- },
577
- ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ import_react5.default.createElement(
578
- "button",
539
+ dropSquareStyle: theme.state.dropSquare,
540
+ onPieceDrag: ({ piece, square }) => {
541
+ if (piece.pieceType[0] === turn) {
542
+ setActiveSquare(square);
543
+ }
544
+ },
545
+ onPieceDrop: ({ sourceSquare, targetSquare }) => {
546
+ setActiveSquare(null);
547
+ const moveData = {
548
+ from: sourceSquare,
549
+ to: targetSquare
550
+ };
551
+ if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
552
+ setPromotionMove(moveData);
553
+ return false;
554
+ }
555
+ return makeMove(moveData);
556
+ },
557
+ onSquareClick: ({ square }) => {
558
+ if (square.match(/^[a-h][1-8]$/)) {
559
+ onSquareClick(square);
560
+ }
561
+ },
562
+ onSquareRightClick,
563
+ allowDrawingArrows: true,
564
+ animationDurationInMs: game.history().length === 0 ? 0 : 300
565
+ };
566
+ const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
567
+ const mergedStyle = {
568
+ ...userStyle,
569
+ position: "relative"
570
+ };
571
+ return /* @__PURE__ */ import_react5.default.createElement("div", { ref, className, style: mergedStyle, ...rest }, /* @__PURE__ */ import_react5.default.createElement(import_react_chessboard.Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(
572
+ "div",
579
573
  {
580
- key: piece,
581
- onClick: () => onPromotionPieceSelect(piece),
574
+ onClick: () => setPromotionMove(null),
582
575
  onContextMenu: (e) => {
583
576
  e.preventDefault();
577
+ setPromotionMove(null);
584
578
  },
585
579
  style: {
586
- width: "100%",
587
- aspectRatio: "1",
580
+ position: "absolute",
581
+ top: 0,
582
+ left: 0,
583
+ right: 0,
584
+ bottom: 0,
585
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
586
+ zIndex: 1e3
587
+ }
588
+ }
589
+ ), /* @__PURE__ */ import_react5.default.createElement(
590
+ "div",
591
+ {
592
+ style: {
593
+ position: "absolute",
594
+ top: ((_b = (_a = promotionMove.to) == null ? void 0 : _a[1]) == null ? void 0 : _b.includes("8")) ? 0 : "auto",
595
+ bottom: ((_c = promotionMove.to) == null ? void 0 : _c[1].includes("1")) ? 0 : "auto",
596
+ left: promotionSquareLeft,
597
+ backgroundColor: "white",
598
+ width: squareWidth,
599
+ zIndex: 1001,
588
600
  display: "flex",
589
- alignItems: "center",
590
- justifyContent: "center",
591
- padding: 0,
592
- border: "none",
593
- cursor: "pointer",
594
- backgroundColor: "white"
595
- },
596
- onMouseEnter: (e) => {
597
- e.currentTarget.style.backgroundColor = "#f0f0f0";
598
- },
599
- onMouseLeave: (e) => {
600
- e.currentTarget.style.backgroundColor = "white";
601
+ flexDirection: "column",
602
+ boxShadow: "0 0 10px 0 rgba(0, 0, 0, 0.5)"
601
603
  }
602
604
  },
603
- import_react_chessboard.defaultPieces[`${turn}${piece.toUpperCase()}`]()
604
- ))
605
- )));
606
- };
605
+ ["q", "r", "n", "b"].map((piece) => /* @__PURE__ */ import_react5.default.createElement(
606
+ "button",
607
+ {
608
+ key: piece,
609
+ onClick: () => onPromotionPieceSelect(piece),
610
+ onContextMenu: (e) => {
611
+ e.preventDefault();
612
+ },
613
+ style: {
614
+ width: "100%",
615
+ aspectRatio: "1",
616
+ display: "flex",
617
+ alignItems: "center",
618
+ justifyContent: "center",
619
+ padding: 0,
620
+ border: "none",
621
+ cursor: "pointer",
622
+ backgroundColor: "white"
623
+ },
624
+ onMouseEnter: (e) => {
625
+ e.currentTarget.style.backgroundColor = "#f0f0f0";
626
+ },
627
+ onMouseLeave: (e) => {
628
+ e.currentTarget.style.backgroundColor = "white";
629
+ }
630
+ },
631
+ import_react_chessboard.defaultPieces[`${turn}${piece.toUpperCase()}`]()
632
+ ))
633
+ )));
634
+ }
635
+ );
636
+ Board.displayName = "ChessGame.Board";
607
637
 
608
638
  // src/components/ChessGame/parts/Sounds.tsx
609
639
  var import_react7 = require("react");
@@ -665,6 +695,7 @@ var Sounds = ({ sounds }) => {
665
695
  useBoardSounds(customSoundsAudios);
666
696
  return null;
667
697
  };
698
+ Sounds.displayName = "ChessGame.Sounds";
668
699
 
669
700
  // src/hooks/useKeyboardControls.ts
670
701
  var import_react8 = require("react");
@@ -708,15 +739,64 @@ var KeyboardControls2 = ({
708
739
  useKeyboardControls(keyboardControls);
709
740
  return null;
710
741
  };
742
+ KeyboardControls2.displayName = "ChessGame.KeyboardControls";
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
+ };
711
787
 
712
788
  // src/components/ChessGame/index.ts
713
789
  var ChessGame = {
714
790
  Root,
715
791
  Board,
716
792
  Sounds,
717
- KeyboardControls: KeyboardControls2
793
+ KeyboardControls: KeyboardControls2,
794
+ Clock
718
795
  };
719
796
 
797
+ // src/index.ts
798
+ var import_react_chess_clock3 = require("@react-chess-tools/react-chess-clock");
799
+
720
800
  // src/theme/presets.ts
721
801
  var lichessTheme = {
722
802
  board: {
@@ -759,6 +839,7 @@ var themes = {
759
839
  };
760
840
  // Annotate the CommonJS export names for ESM import in node:
761
841
  0 && (module.exports = {
842
+ ChessClock,
762
843
  ChessGame,
763
844
  ChessGameThemeContext,
764
845
  chessComTheme,