react-chess-replay-trainer 0.0.3 → 0.0.5

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.
@@ -28,6 +28,12 @@ export interface ReplayTrainerState {
28
28
  /** Revealed/expected move at the current ply (set after a miss or reveal). */
29
29
  expectedSan: string | null;
30
30
  expectedUci: string | null;
31
+ /** Destination square of the last correct guess (green check overlay). */
32
+ correctMoveSquare: string | null;
33
+ /** FEN shown on the board (includes a pending correct-move ply). */
34
+ displayFen: string;
35
+ /** UCI of the move that produced the current board position. */
36
+ lastMoveUci: string | null;
31
37
  canPrev: boolean;
32
38
  canNext: boolean;
33
39
  goFirst: () => void;
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
3
- import { createExpectedMoveDropHandler, ThemeProvider, ChessboardDnDProvider, HighlightChessboard, PlyNavigation, AnalysisErrorBoundary, AnalysisBoard } from 'react-chess-core';
3
+ import { useCorrectMoveFeedback, lastMoveUciAtPly, createExpectedMoveDropHandler, fenAfterUci, ThemeProvider, ChessboardDnDProvider, HighlightChessboard, PlyNavigation, AnalysisErrorBoundary, AnalysisBoard } from 'react-chess-core';
4
4
  export { AnalysisBoard, AnalysisBoardCore, AnalysisErrorBoundary, DEFAULT_ANALYSIS_LAYOUT, DefaultPlyNavigation, PlyNavigation, MISS_MOVE_ANIMATION_MS as REPLAY_MISS_MOVE_ANIMATION_MS, MISS_REFUTATION_MAX_WAIT_MS as REPLAY_MISS_REFUTATION_MAX_WAIT_MS, MISS_REFUTATION_PAUSE_MS as REPLAY_MISS_REFUTATION_PAUSE_MS, MISS_WRONG_PAUSE_MS as REPLAY_MISS_WRONG_PAUSE_MS, REFUTATION_EVAL_GAP_CP as REPLAY_REFUTATION_EVAL_GAP_CP, REFUTATION_EVAL_GAP_PAWNS as REPLAY_REFUTATION_EVAL_GAP_PAWNS, defaultRenderPlyNavigation, fenAfterUci, getMissDisplay as getReplayMissDisplay, refutationEvalGapCp, refutationFromEvaluation, refutationEngineOptions as replayRefutationEngineOptions, uciFromDrop, useMissBoard as useReplayMissBoard, useMissSequence as useReplayMissSequence, useMissRefutation as useReplayRefutation } from 'react-chess-core';
5
5
  import { Chess } from 'chess.js';
6
6
 
@@ -78,6 +78,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
78
78
  const [expectedSan, setExpectedSan] = useState(null);
79
79
  const [autoplayActive, setAutoplayActive] = useState(false);
80
80
  const [expectedUci, setExpectedUci] = useState(null);
81
+ const [feedbackFen, setFeedbackFen] = useState(null);
82
+ const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = useCorrectMoveFeedback();
81
83
  const fetchGameRef = useRef(fetchGame);
82
84
  fetchGameRef.current = fetchGame;
83
85
  const onMissRef = useRef(onMiss);
@@ -88,6 +90,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
88
90
  const completedFiredRef = useRef(false);
89
91
  const modeRef = useRef('browse');
90
92
  const trainColorRef = useRef('both');
93
+ const showingCorrectMoveRef = useRef(false);
94
+ showingCorrectMoveRef.current = isShowingCorrectMove;
91
95
  useEffect(() => {
92
96
  let cancelled = false;
93
97
  setLoading(true);
@@ -131,6 +135,14 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
131
135
  const movesUci = useMemo(() => { var _a; return (_a = game === null || game === void 0 ? void 0 : game.movesUci) !== null && _a !== void 0 ? _a : []; }, [game]);
132
136
  const totalPly = movesUci.length;
133
137
  const fen = useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
138
+ const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
139
+ const lastMoveUci = useMemo(() => {
140
+ var _a;
141
+ if (feedbackFen) {
142
+ return (_a = movesUci[plyIndex]) !== null && _a !== void 0 ? _a : null;
143
+ }
144
+ return lastMoveUciAtPly(movesUci, plyIndex);
145
+ }, [feedbackFen, movesUci, plyIndex]);
134
146
  const complete = plyIndex >= totalPly && totalPly > 0;
135
147
  const sideToMove$1 = sideToMove(fen);
136
148
  const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
@@ -140,7 +152,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
140
152
  setFeedback(null);
141
153
  setExpectedSan(null);
142
154
  setExpectedUci(null);
143
- }, []);
155
+ setFeedbackFen(null);
156
+ clearCorrectMoveFeedback();
157
+ }, [clearCorrectMoveFeedback]);
144
158
  const stopAutoplay = useCallback(() => {
145
159
  setAutoplayActive(false);
146
160
  }, []);
@@ -220,12 +234,21 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
220
234
  expectedUci: movesUci[plyIndex],
221
235
  enabled: modeRef.current === 'train' &&
222
236
  !complete &&
237
+ !showingCorrectMoveRef.current &&
223
238
  isTrainSideToMove(trainColorRef.current, sideToMove$1),
224
- onCorrect: () => {
239
+ onCorrect: (uci) => {
225
240
  setFeedback('correct');
226
241
  setExpectedSan(null);
227
242
  setExpectedUci(null);
228
- setPlyIndex((p) => p + 1);
243
+ const nextFen = fenAfterUci(fen, uci);
244
+ if (nextFen) {
245
+ setFeedbackFen(nextFen);
246
+ }
247
+ showCorrectMove(uci.slice(2, 4), () => {
248
+ setFeedbackFen(null);
249
+ setFeedback(null);
250
+ setPlyIndex((p) => p + 1);
251
+ });
229
252
  },
230
253
  onIncorrect: () => {
231
254
  var _a, _b;
@@ -238,7 +261,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
238
261
  setExpectedUci(expectedUci);
239
262
  recordMiss(plyIndex);
240
263
  },
241
- })(source, target, piece), [complete, movesUci, plyIndex, fen, game, recordMiss, sideToMove$1]);
264
+ })(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
242
265
  useEffect(() => {
243
266
  var _a;
244
267
  if (mode === 'train' && complete && !completedFiredRef.current) {
@@ -267,12 +290,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
267
290
  if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
268
291
  return;
269
292
  }
293
+ if (isShowingCorrectMove) {
294
+ return;
295
+ }
270
296
  const id = setTimeout(() => {
271
297
  setPlyIndex((p) => (p < totalPly ? p + 1 : p));
272
298
  clearTransient();
273
299
  }, OPPONENT_MOVE_DELAY_MS);
274
300
  return () => clearTimeout(id);
275
- }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient]);
301
+ }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
276
302
  return {
277
303
  game,
278
304
  loading,
@@ -288,6 +314,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
288
314
  feedback,
289
315
  expectedSan,
290
316
  expectedUci,
317
+ correctMoveSquare,
318
+ displayFen,
319
+ lastMoveUci,
291
320
  canPrev: plyIndex > 0,
292
321
  canNext: plyIndex < totalPly,
293
322
  goFirst,
@@ -437,13 +466,13 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
437
466
  ]
438
467
  : [];
439
468
  const draggable = training && !state.complete;
440
- return (jsxs(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxs("div", { style: headerStyle, children: [jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: state.fen, boardOrientation: boardOrientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => {
469
+ return (jsxs(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxs("div", { style: headerStyle, children: [jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
441
470
  if (state.trainColor === 'white')
442
471
  return piece[0] === 'w';
443
472
  if (state.trainColor === 'black')
444
473
  return piece[0] === 'b';
445
474
  return piece[0] === state.sideToMove;
446
- }, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsx(PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxs(Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
475
+ }, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, lastMoveUci: state.lastMoveUci, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsx(PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxs(Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
447
476
  ? ` · ${state.sideToMove === 'b' ? 'Black' : 'White'} to move`
448
477
  : state.isUserTurn
449
478
  ? ' · Your move'
package/dist/index.js CHANGED
@@ -79,6 +79,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
79
79
  const [expectedSan, setExpectedSan] = react.useState(null);
80
80
  const [autoplayActive, setAutoplayActive] = react.useState(false);
81
81
  const [expectedUci, setExpectedUci] = react.useState(null);
82
+ const [feedbackFen, setFeedbackFen] = react.useState(null);
83
+ const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = reactChessCore.useCorrectMoveFeedback();
82
84
  const fetchGameRef = react.useRef(fetchGame);
83
85
  fetchGameRef.current = fetchGame;
84
86
  const onMissRef = react.useRef(onMiss);
@@ -89,6 +91,8 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
89
91
  const completedFiredRef = react.useRef(false);
90
92
  const modeRef = react.useRef('browse');
91
93
  const trainColorRef = react.useRef('both');
94
+ const showingCorrectMoveRef = react.useRef(false);
95
+ showingCorrectMoveRef.current = isShowingCorrectMove;
92
96
  react.useEffect(() => {
93
97
  let cancelled = false;
94
98
  setLoading(true);
@@ -132,6 +136,14 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
132
136
  const movesUci = react.useMemo(() => { var _a; return (_a = game === null || game === void 0 ? void 0 : game.movesUci) !== null && _a !== void 0 ? _a : []; }, [game]);
133
137
  const totalPly = movesUci.length;
134
138
  const fen = react.useMemo(() => fenAtPly(movesUci, plyIndex), [movesUci, plyIndex]);
139
+ const displayFen = feedbackFen !== null && feedbackFen !== void 0 ? feedbackFen : fen;
140
+ const lastMoveUci = react.useMemo(() => {
141
+ var _a;
142
+ if (feedbackFen) {
143
+ return (_a = movesUci[plyIndex]) !== null && _a !== void 0 ? _a : null;
144
+ }
145
+ return reactChessCore.lastMoveUciAtPly(movesUci, plyIndex);
146
+ }, [feedbackFen, movesUci, plyIndex]);
135
147
  const complete = plyIndex >= totalPly && totalPly > 0;
136
148
  const sideToMove$1 = sideToMove(fen);
137
149
  const isUserTurn = isTrainSideToMove(trainColor, sideToMove$1);
@@ -141,7 +153,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
141
153
  setFeedback(null);
142
154
  setExpectedSan(null);
143
155
  setExpectedUci(null);
144
- }, []);
156
+ setFeedbackFen(null);
157
+ clearCorrectMoveFeedback();
158
+ }, [clearCorrectMoveFeedback]);
145
159
  const stopAutoplay = react.useCallback(() => {
146
160
  setAutoplayActive(false);
147
161
  }, []);
@@ -221,12 +235,21 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
221
235
  expectedUci: movesUci[plyIndex],
222
236
  enabled: modeRef.current === 'train' &&
223
237
  !complete &&
238
+ !showingCorrectMoveRef.current &&
224
239
  isTrainSideToMove(trainColorRef.current, sideToMove$1),
225
- onCorrect: () => {
240
+ onCorrect: (uci) => {
226
241
  setFeedback('correct');
227
242
  setExpectedSan(null);
228
243
  setExpectedUci(null);
229
- setPlyIndex((p) => p + 1);
244
+ const nextFen = reactChessCore.fenAfterUci(fen, uci);
245
+ if (nextFen) {
246
+ setFeedbackFen(nextFen);
247
+ }
248
+ showCorrectMove(uci.slice(2, 4), () => {
249
+ setFeedbackFen(null);
250
+ setFeedback(null);
251
+ setPlyIndex((p) => p + 1);
252
+ });
230
253
  },
231
254
  onIncorrect: () => {
232
255
  var _a, _b;
@@ -239,7 +262,7 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
239
262
  setExpectedUci(expectedUci);
240
263
  recordMiss(plyIndex);
241
264
  },
242
- })(source, target, piece), [complete, movesUci, plyIndex, fen, game, recordMiss, sideToMove$1]);
265
+ })(source, target, piece), [complete, fen, movesUci, plyIndex, game, recordMiss, sideToMove$1, showCorrectMove]);
243
266
  react.useEffect(() => {
244
267
  var _a;
245
268
  if (mode === 'train' && complete && !completedFiredRef.current) {
@@ -268,12 +291,15 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
268
291
  if (mode !== 'train' || complete || trainColor === 'both' || isUserTurn) {
269
292
  return;
270
293
  }
294
+ if (isShowingCorrectMove) {
295
+ return;
296
+ }
271
297
  const id = setTimeout(() => {
272
298
  setPlyIndex((p) => (p < totalPly ? p + 1 : p));
273
299
  clearTransient();
274
300
  }, OPPONENT_MOVE_DELAY_MS);
275
301
  return () => clearTimeout(id);
276
- }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient]);
302
+ }, [mode, complete, trainColor, isUserTurn, plyIndex, totalPly, clearTransient, isShowingCorrectMove]);
277
303
  return {
278
304
  game,
279
305
  loading,
@@ -289,6 +315,9 @@ function useReplayTrainer({ gameId, startFen, fetchGame, onMiss, onComplete, })
289
315
  feedback,
290
316
  expectedSan,
291
317
  expectedUci,
318
+ correctMoveSquare,
319
+ displayFen,
320
+ lastMoveUci,
292
321
  canPrev: plyIndex > 0,
293
322
  canNext: plyIndex < totalPly,
294
323
  goFirst,
@@ -438,13 +467,13 @@ const ReplayTrainer = ({ gameId, fetchGame, startFen, onMiss, onComplete, onExit
438
467
  ]
439
468
  : [];
440
469
  const draggable = training && !state.complete;
441
- return (jsxRuntime.jsxs(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxRuntime.jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxRuntime.jsxs("div", { style: headerStyle, children: [jsxRuntime.jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsxRuntime.jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: state.fen, boardOrientation: boardOrientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => {
470
+ return (jsxRuntime.jsxs(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: [jsxRuntime.jsxs("div", { style: mainContainerStyle(boardWidth, colors), children: [jsxRuntime.jsxs("div", { style: headerStyle, children: [jsxRuntime.jsxs("span", { style: playerNameStyle, children: [((_b = game.white) !== null && _b !== void 0 ? _b : 'White'), " vs ", ((_c = game.black) !== null && _c !== void 0 ? _c : 'Black')] }), game.result && jsxRuntime.jsx("span", { style: subtleTextStyle(colors), children: game.result })] }), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: state.correctMoveSquare, position: state.displayFen, boardOrientation: boardOrientation, arePiecesDraggable: draggable && !state.correctMoveSquare, isDraggablePiece: ({ piece }) => {
442
471
  if (state.trainColor === 'white')
443
472
  return piece[0] === 'w';
444
473
  if (state.trainColor === 'black')
445
474
  return piece[0] === 'b';
446
475
  return piece[0] === state.sideToMove;
447
- }, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsxRuntime.jsx(reactChessCore.PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsxRuntime.jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxRuntime.jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
476
+ }, onPieceDrop: (source, target, piece) => state.handleDrop(source, target, piece), customArrows: customArrows, lastMoveUci: state.lastMoveUci, promotionDialogVariant: "modal", areArrowsAllowed: false, customBoardStyle: customBoardStyle }) }), jsxRuntime.jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 4 }, children: [jsxRuntime.jsx(reactChessCore.PlyNavigation, { plyIndex: state.plyIndex, totalPly: state.totalPly, canPrev: state.canPrev, canNext: state.canNext, onGoFirst: state.goFirst, onGoPrev: state.goPrev, onGoNext: state.goNext, onGoLast: state.goLast, onGoTo: state.goTo, theme: theme, showScrubber: showPlyScrubber, renderPlyNavigation: renderPlyNavigation }), jsxRuntime.jsx("button", { type: "button", onClick: state.toggleAutoplay, disabled: !state.autoplayActive && !state.canNext, style: buttonStyle(colors, state.autoplayActive ? 'ghost' : 'primary'), "aria-label": state.autoplayActive ? 'Stop autoplay' : 'Autoplay game', children: state.autoplayActive ? 'Stop' : 'Play' })] }), jsxRuntime.jsxs("div", { style: statusLineStyle(colors), children: ["Half move ", Math.min(state.plyIndex + (state.complete ? 0 : 1), state.totalPly), " of", ' ', state.totalPly, training && !state.complete && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [` · ${TRAIN_COLOR_LABEL[state.trainColor]}`, state.trainColor === 'both'
448
477
  ? ` · ${state.sideToMove === 'b' ? 'Black' : 'White'} to move`
449
478
  : state.isUserTurn
450
479
  ? ' · Your move'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-replay-trainer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "React component for browsing a chess game and drilling its moves, reporting misses (uses react-chess-core for board and analysis)",
5
5
  "license": "MIT",
6
6
  "author": "Robert Blackwell",