react-chess-explorer 0.0.4 → 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.
@@ -16,6 +16,7 @@ export declare function useGameReplayTraining({ gameId, startFen, fetchGame, }:
16
16
  error: string | null;
17
17
  feedback: GameReplayFeedback;
18
18
  lastExpectedSan: string | null;
19
+ lastMoveUci: any;
19
20
  expectedSan: string | undefined;
20
21
  handlePieceDrop: (sourceSquare: string, targetSquare: string, piece: string) => boolean;
21
22
  revealMove: () => void;
@@ -2,6 +2,8 @@ export type PositionHistoryEntry = {
2
2
  fen: string;
3
3
  /** SAN of the move played from the previous entry to reach this FEN. */
4
4
  lastSan?: string;
5
+ /** UCI of the move played from the previous entry to reach this FEN. */
6
+ lastUci?: string;
5
7
  };
6
8
  export declare function initialHistoryState(initialFen: string, initialLineSans?: string[]): {
7
9
  history: PositionHistoryEntry[];
@@ -13,14 +15,17 @@ export declare function usePositionHistory(initialFen: string, initialLineSans?:
13
15
  lineSans: string[];
14
16
  forwardSans: string[];
15
17
  currentFen: string;
16
- pushEntry: (fen: string, lastSan?: string) => PositionHistoryEntry;
18
+ lastMoveUci: string | null;
19
+ pushEntry: (fen: string, lastSan?: string, lastUci?: string) => PositionHistoryEntry;
17
20
  pushEntries: (entries: {
18
21
  fen: string;
19
22
  lastSan: string;
23
+ lastUci?: string;
20
24
  }[]) => void;
21
25
  replaceLineEntries: (startFen: string, entries: {
22
26
  fen: string;
23
27
  lastSan: string;
28
+ lastUci?: string;
24
29
  }[]) => void;
25
30
  goBack: () => PositionHistoryEntry | null;
26
31
  goForward: () => PositionHistoryEntry | null;
@@ -28,6 +28,7 @@ export declare function usePositionReferenceData({ fenProp, onFenChange, initial
28
28
  canGoForward: boolean;
29
29
  forwardSans: string[];
30
30
  selectedVariationKey: string | undefined;
31
+ lastMoveUci: string | null;
31
32
  setSources: import("react").Dispatch<import("react").SetStateAction<GameSource[]>>;
32
33
  setGamesMoveFilterUci: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
33
34
  handleMoveSelect: (move: PositionMoveApiDto) => void;
@@ -10,6 +10,7 @@ export declare function applyBoardMove(fen: string, sourceSquare: string, target
10
10
  export type LineSansEntry = {
11
11
  fen: string;
12
12
  lastSan: string;
13
+ lastUci: string;
13
14
  };
14
15
  /** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
15
16
  export declare function applyLineSans(startFen: string, sans: string[]): {
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Chess } from 'chess.js';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { ThemeProvider, HighlightChessboard, usePositionKeyboardNav, ChessboardDnDProvider } from 'react-chess-core';
3
+ import { ThemeProvider, HighlightChessboard, usePositionKeyboardNav, ChessboardDnDProvider, lastMoveUciAtPly } from 'react-chess-core';
4
4
  import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
5
5
 
6
6
  /** Standard start position — placeholder until game replay is implemented. */
@@ -39,6 +39,7 @@ function applyBoardMove(fen, sourceSquare, targetSquare, piece) {
39
39
  }
40
40
  /** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
41
41
  function applyLineSans(startFen, sans) {
42
+ var _a;
42
43
  const chess = new Chess(startFen);
43
44
  const entries = [];
44
45
  for (const san of sans) {
@@ -46,9 +47,10 @@ function applyLineSans(startFen, sans) {
46
47
  const move = chess.move(san);
47
48
  if (!move)
48
49
  return null;
49
- entries.push({ fen: chess.fen(), lastSan: move.san });
50
+ const lastUci = `${move.from}${move.to}${(_a = move.promotion) !== null && _a !== void 0 ? _a : ""}`;
51
+ entries.push({ fen: chess.fen(), lastSan: move.san, lastUci });
50
52
  }
51
- catch (_a) {
53
+ catch (_b) {
52
54
  return null;
53
55
  }
54
56
  }
@@ -523,19 +525,19 @@ function initialHistoryState(initialFen, initialLineSans) {
523
525
  };
524
526
  }
525
527
  function usePositionHistory(initialFen, initialLineSans) {
526
- var _a, _b;
528
+ var _a, _b, _c, _d;
527
529
  const [history, setHistory] = useState(() => initialHistoryState(initialFen, initialLineSans).history);
528
530
  const [historyIndex, setHistoryIndex] = useState(() => initialHistoryState(initialFen, initialLineSans).historyIndex);
529
531
  const canGoBack = historyIndex > 0;
530
532
  const canGoForward = historyIndex < history.length - 1;
531
- const pushEntry = useCallback((fen, lastSan) => {
533
+ const pushEntry = useCallback((fen, lastSan, lastUci) => {
532
534
  setHistory((prev) => {
533
535
  const trimmed = prev.slice(0, historyIndex + 1);
534
- const next = [...trimmed, { fen, lastSan }];
536
+ const next = [...trimmed, { fen, lastSan, lastUci }];
535
537
  setHistoryIndex(next.length - 1);
536
538
  return next;
537
539
  });
538
- return { fen, lastSan };
540
+ return { fen, lastSan, lastUci };
539
541
  }, [historyIndex]);
540
542
  const goBack = useCallback(() => {
541
543
  if (historyIndex <= 0)
@@ -596,12 +598,14 @@ function usePositionHistory(initialFen, initialLineSans) {
596
598
  setHistory([{ fen: startFen }, ...entries]);
597
599
  setHistoryIndex(entries.length);
598
600
  }, []);
601
+ const lastMoveUci = historyIndex > 0 ? (_d = (_c = history[historyIndex]) === null || _c === void 0 ? void 0 : _c.lastUci) !== null && _d !== void 0 ? _d : null : null;
599
602
  return {
600
603
  canGoBack,
601
604
  canGoForward,
602
605
  lineSans,
603
606
  forwardSans,
604
607
  currentFen,
608
+ lastMoveUci,
605
609
  pushEntry,
606
610
  pushEntries,
607
611
  replaceLineEntries,
@@ -646,7 +650,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
646
650
  }
647
651
  isAnimatingVariationRef.current = false;
648
652
  }, []);
649
- const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, } = usePositionHistory(initialFen, initialLineSans);
653
+ const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, lastMoveUci: historyLastMoveUci, } = usePositionHistory(initialFen, initialLineSans);
650
654
  /** FEN used for explorer API queries — follows move history, not animation frames. */
651
655
  const queryFen = currentFen;
652
656
  const initialLineKey = useMemo(() => { var _a; return (_a = initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.join("|")) !== null && _a !== void 0 ? _a : ""; }, [initialLineSans]);
@@ -681,6 +685,10 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
681
685
  }), [sources]);
682
686
  const lastAppliedLineKeyRef = useRef(initialLineKey);
683
687
  useEffect(() => () => cancelVariationAnimation(), [cancelVariationAnimation]);
688
+ // Hold URL line callbacks until history catches up after an external line change.
689
+ useEffect(() => {
690
+ readyForLineSyncRef.current = false;
691
+ }, [initialLineKey]);
684
692
  // Apply URL line changes only when the external line key changes — not when the
685
693
  // user clicks moves (internal lineSans updates must not re-sync from stale props).
686
694
  useEffect(() => {
@@ -891,7 +899,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
891
899
  const nextFen = fenAfterUci(queryFen, move.uci);
892
900
  if (!nextFen)
893
901
  return;
894
- pushEntry(nextFen, move.san);
902
+ pushEntry(nextFen, move.san, move.uci);
895
903
  setSelectedVariationKey(undefined);
896
904
  applyNavigation(nextFen, true);
897
905
  }, [queryFen, pushEntry, applyNavigation, cancelVariationAnimation]);
@@ -905,7 +913,11 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
905
913
  const nextFen = fenAfterUci(lineFen, line.uciPath[i]);
906
914
  if (!nextFen)
907
915
  return;
908
- entries.push({ fen: nextFen, lastSan: line.moves[i].san });
916
+ entries.push({
917
+ fen: nextFen,
918
+ lastSan: line.moves[i].san,
919
+ lastUci: line.uciPath[i],
920
+ });
909
921
  lineFen = nextFen;
910
922
  }
911
923
  if (entries.length === 0)
@@ -940,7 +952,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
940
952
  const result = applyBoardMove(queryFen, sourceSquare, targetSquare, piece);
941
953
  if (!result)
942
954
  return false;
943
- pushEntry(result.fen, result.san);
955
+ pushEntry(result.fen, result.san, result.uci);
944
956
  setSelectedVariationKey(undefined);
945
957
  applyNavigation(result.fen, true);
946
958
  return true;
@@ -1017,6 +1029,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
1017
1029
  canGoForward,
1018
1030
  forwardSans,
1019
1031
  selectedVariationKey,
1032
+ lastMoveUci: historyLastMoveUci,
1020
1033
  setSources,
1021
1034
  setGamesMoveFilterUci,
1022
1035
  handleMoveSelect,
@@ -1115,7 +1128,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
1115
1128
  fetchPositionGames,
1116
1129
  fetchPositionVariations,
1117
1130
  });
1118
- const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
1131
+ const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, lastMoveUci, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
1119
1132
  usePositionKeyboardNav({
1120
1133
  enabled: keyboardNav,
1121
1134
  canPrev: canGoBack,
@@ -1179,7 +1192,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
1179
1192
  overflow: fillHeight ? "hidden" : "visible",
1180
1193
  boxSizing: "border-box",
1181
1194
  };
1182
- const board = (jsxs(Fragment, { children: [jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
1195
+ const board = (jsxs(Fragment, { children: [jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
1183
1196
  const referencePanel = (jsx(DefaultReferencePanel, { theme: theme, fillHeight: fillHeight, status: renderStatus({
1184
1197
  error,
1185
1198
  loading: showPositionLoading,
@@ -1301,6 +1314,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
1301
1314
  const complete = game ? plyIndex >= game.movesUci.length : false;
1302
1315
  const expectedUci = game === null || game === void 0 ? void 0 : game.movesUci[plyIndex];
1303
1316
  const expectedSan = game === null || game === void 0 ? void 0 : game.movesSan[plyIndex];
1317
+ const lastMoveUci = useMemo(() => (game ? lastMoveUciAtPly(game.movesUci, plyIndex) : null), [game, plyIndex]);
1304
1318
  const handlePieceDrop = useCallback((sourceSquare, targetSquare, piece) => {
1305
1319
  if (!game || complete || !expectedUci)
1306
1320
  return false;
@@ -1334,6 +1348,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
1334
1348
  error,
1335
1349
  feedback,
1336
1350
  lastExpectedSan,
1351
+ lastMoveUci,
1337
1352
  expectedSan,
1338
1353
  handlePieceDrop,
1339
1354
  revealMove,
@@ -1354,7 +1369,7 @@ const defaultButtonStyle = {
1354
1369
  cursor: "pointer",
1355
1370
  };
1356
1371
  const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, renderStatus, }) => {
1357
- const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
1372
+ const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, lastMoveUci, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
1358
1373
  const status = renderStatus === null || renderStatus === void 0 ? void 0 : renderStatus({
1359
1374
  loading,
1360
1375
  error,
@@ -1374,7 +1389,7 @@ const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark"
1374
1389
  !error &&
1375
1390
  !complete &&
1376
1391
  feedback === "incorrect" &&
1377
- lastExpectedSan && (jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
1392
+ lastExpectedSan && (jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
1378
1393
  };
1379
1394
 
1380
1395
  const mockPosition = {
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ function applyBoardMove(fen, sourceSquare, targetSquare, piece) {
41
41
  }
42
42
  /** Play a SAN sequence from a start FEN; returns null if any move is illegal. */
43
43
  function applyLineSans(startFen, sans) {
44
+ var _a;
44
45
  const chess = new chess_js.Chess(startFen);
45
46
  const entries = [];
46
47
  for (const san of sans) {
@@ -48,9 +49,10 @@ function applyLineSans(startFen, sans) {
48
49
  const move = chess.move(san);
49
50
  if (!move)
50
51
  return null;
51
- entries.push({ fen: chess.fen(), lastSan: move.san });
52
+ const lastUci = `${move.from}${move.to}${(_a = move.promotion) !== null && _a !== void 0 ? _a : ""}`;
53
+ entries.push({ fen: chess.fen(), lastSan: move.san, lastUci });
52
54
  }
53
- catch (_a) {
55
+ catch (_b) {
54
56
  return null;
55
57
  }
56
58
  }
@@ -525,19 +527,19 @@ function initialHistoryState(initialFen, initialLineSans) {
525
527
  };
526
528
  }
527
529
  function usePositionHistory(initialFen, initialLineSans) {
528
- var _a, _b;
530
+ var _a, _b, _c, _d;
529
531
  const [history, setHistory] = react.useState(() => initialHistoryState(initialFen, initialLineSans).history);
530
532
  const [historyIndex, setHistoryIndex] = react.useState(() => initialHistoryState(initialFen, initialLineSans).historyIndex);
531
533
  const canGoBack = historyIndex > 0;
532
534
  const canGoForward = historyIndex < history.length - 1;
533
- const pushEntry = react.useCallback((fen, lastSan) => {
535
+ const pushEntry = react.useCallback((fen, lastSan, lastUci) => {
534
536
  setHistory((prev) => {
535
537
  const trimmed = prev.slice(0, historyIndex + 1);
536
- const next = [...trimmed, { fen, lastSan }];
538
+ const next = [...trimmed, { fen, lastSan, lastUci }];
537
539
  setHistoryIndex(next.length - 1);
538
540
  return next;
539
541
  });
540
- return { fen, lastSan };
542
+ return { fen, lastSan, lastUci };
541
543
  }, [historyIndex]);
542
544
  const goBack = react.useCallback(() => {
543
545
  if (historyIndex <= 0)
@@ -598,12 +600,14 @@ function usePositionHistory(initialFen, initialLineSans) {
598
600
  setHistory([{ fen: startFen }, ...entries]);
599
601
  setHistoryIndex(entries.length);
600
602
  }, []);
603
+ const lastMoveUci = historyIndex > 0 ? (_d = (_c = history[historyIndex]) === null || _c === void 0 ? void 0 : _c.lastUci) !== null && _d !== void 0 ? _d : null : null;
601
604
  return {
602
605
  canGoBack,
603
606
  canGoForward,
604
607
  lineSans,
605
608
  forwardSans,
606
609
  currentFen,
610
+ lastMoveUci,
607
611
  pushEntry,
608
612
  pushEntries,
609
613
  replaceLineEntries,
@@ -648,7 +652,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
648
652
  }
649
653
  isAnimatingVariationRef.current = false;
650
654
  }, []);
651
- const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, } = usePositionHistory(initialFen, initialLineSans);
655
+ const { canGoBack, canGoForward, lineSans, forwardSans, currentFen, pushEntry, pushEntries, replaceLineEntries, goBack, goForward, goFirst, goLast, resetHistory, lastMoveUci: historyLastMoveUci, } = usePositionHistory(initialFen, initialLineSans);
652
656
  /** FEN used for explorer API queries — follows move history, not animation frames. */
653
657
  const queryFen = currentFen;
654
658
  const initialLineKey = react.useMemo(() => { var _a; return (_a = initialLineSans === null || initialLineSans === void 0 ? void 0 : initialLineSans.join("|")) !== null && _a !== void 0 ? _a : ""; }, [initialLineSans]);
@@ -683,6 +687,10 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
683
687
  }), [sources]);
684
688
  const lastAppliedLineKeyRef = react.useRef(initialLineKey);
685
689
  react.useEffect(() => () => cancelVariationAnimation(), [cancelVariationAnimation]);
690
+ // Hold URL line callbacks until history catches up after an external line change.
691
+ react.useEffect(() => {
692
+ readyForLineSyncRef.current = false;
693
+ }, [initialLineKey]);
686
694
  // Apply URL line changes only when the external line key changes — not when the
687
695
  // user clicks moves (internal lineSans updates must not re-sync from stale props).
688
696
  react.useEffect(() => {
@@ -893,7 +901,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
893
901
  const nextFen = fenAfterUci(queryFen, move.uci);
894
902
  if (!nextFen)
895
903
  return;
896
- pushEntry(nextFen, move.san);
904
+ pushEntry(nextFen, move.san, move.uci);
897
905
  setSelectedVariationKey(undefined);
898
906
  applyNavigation(nextFen, true);
899
907
  }, [queryFen, pushEntry, applyNavigation, cancelVariationAnimation]);
@@ -907,7 +915,11 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
907
915
  const nextFen = fenAfterUci(lineFen, line.uciPath[i]);
908
916
  if (!nextFen)
909
917
  return;
910
- entries.push({ fen: nextFen, lastSan: line.moves[i].san });
918
+ entries.push({
919
+ fen: nextFen,
920
+ lastSan: line.moves[i].san,
921
+ lastUci: line.uciPath[i],
922
+ });
911
923
  lineFen = nextFen;
912
924
  }
913
925
  if (entries.length === 0)
@@ -942,7 +954,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
942
954
  const result = applyBoardMove(queryFen, sourceSquare, targetSquare, piece);
943
955
  if (!result)
944
956
  return false;
945
- pushEntry(result.fen, result.san);
957
+ pushEntry(result.fen, result.san, result.uci);
946
958
  setSelectedVariationKey(undefined);
947
959
  applyNavigation(result.fen, true);
948
960
  return true;
@@ -1019,6 +1031,7 @@ function usePositionReferenceData({ fenProp, onFenChange, initialLineSans, onLin
1019
1031
  canGoForward,
1020
1032
  forwardSans,
1021
1033
  selectedVariationKey,
1034
+ lastMoveUci: historyLastMoveUci,
1022
1035
  setSources,
1023
1036
  setGamesMoveFilterUci,
1024
1037
  handleMoveSelect,
@@ -1117,7 +1130,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
1117
1130
  fetchPositionGames,
1118
1131
  fetchPositionVariations,
1119
1132
  });
1120
- const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
1133
+ const { fen, boardFen, games, sources, lineSans, loading, showPositionLoading, gamesLoading, positionReady, displayMoves, error, lineLabel, canGoBack, canGoForward, forwardSans, selectedVariationKey, lastMoveUci, setSources, handleMoveSelect, handleLineSelect, handlePieceDrop, handleBack, handleForward, handleFirst, handleLast, } = referenceData;
1121
1134
  reactChessCore.usePositionKeyboardNav({
1122
1135
  enabled: keyboardNav,
1123
1136
  canPrev: canGoBack,
@@ -1181,7 +1194,7 @@ const PositionReferenceExplorerCore = ({ fen: fenProp, onFenChange, initialLineS
1181
1194
  overflow: fillHeight ? "hidden" : "visible",
1182
1195
  boxSizing: "border-box",
1183
1196
  };
1184
- const board = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
1197
+ const board = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: boardFen, boardOrientation: boardOrientation, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }, boardOrientation) }), renderBoardNav(boardNavProps)] }));
1185
1198
  const referencePanel = (jsxRuntime.jsx(DefaultReferencePanel, { theme: theme, fillHeight: fillHeight, status: renderStatus({
1186
1199
  error,
1187
1200
  loading: showPositionLoading,
@@ -1303,6 +1316,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
1303
1316
  const complete = game ? plyIndex >= game.movesUci.length : false;
1304
1317
  const expectedUci = game === null || game === void 0 ? void 0 : game.movesUci[plyIndex];
1305
1318
  const expectedSan = game === null || game === void 0 ? void 0 : game.movesSan[plyIndex];
1319
+ const lastMoveUci = react.useMemo(() => (game ? reactChessCore.lastMoveUciAtPly(game.movesUci, plyIndex) : null), [game, plyIndex]);
1306
1320
  const handlePieceDrop = react.useCallback((sourceSquare, targetSquare, piece) => {
1307
1321
  if (!game || complete || !expectedUci)
1308
1322
  return false;
@@ -1336,6 +1350,7 @@ function useGameReplayTraining({ gameId, startFen, fetchGame, }) {
1336
1350
  error,
1337
1351
  feedback,
1338
1352
  lastExpectedSan,
1353
+ lastMoveUci,
1339
1354
  expectedSan,
1340
1355
  handlePieceDrop,
1341
1356
  revealMove,
@@ -1356,7 +1371,7 @@ const defaultButtonStyle = {
1356
1371
  cursor: "pointer",
1357
1372
  };
1358
1373
  const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark", boardWidth = DEFAULT_REFERENCE_LAYOUT.boardWidth, renderStatus, }) => {
1359
- const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
1374
+ const { game, fen, plyIndex, totalPlies, complete, loading, error, feedback, lastExpectedSan, lastMoveUci, handlePieceDrop, revealMove, } = useGameReplayTraining({ gameId, startFen, fetchGame });
1360
1375
  const status = renderStatus === null || renderStatus === void 0 ? void 0 : renderStatus({
1361
1376
  loading,
1362
1377
  error,
@@ -1376,7 +1391,7 @@ const GameReplayTrainer = ({ gameId, startFen, fetchGame, onExit, theme = "dark"
1376
1391
  !error &&
1377
1392
  !complete &&
1378
1393
  feedback === "incorrect" &&
1379
- lastExpectedSan && (jsxRuntime.jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxRuntime.jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
1394
+ lastExpectedSan && (jsxRuntime.jsxs("span", { style: { color: "#ef6c00" }, children: ["Expected ", lastExpectedSan] })), !loading && !error && !complete && feedback === null && (jsxRuntime.jsxs("span", { children: ["Guess move ", plyIndex + 1, " of ", totalPlies] }))] })), jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, position: fen, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, lastMoveUci: lastMoveUci, onPieceDrop: handlePieceDrop, promotionDialogVariant: "modal" }) }), !complete && !loading && !error && (jsxRuntime.jsx("button", { type: "button", style: defaultButtonStyle, onClick: revealMove, children: "Show move" }))] }) }));
1380
1395
  };
1381
1396
 
1382
1397
  const mockPosition = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-explorer",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "React components for browsing and replaying chess games (depends on react-chess-core only)",
5
5
  "license": "MIT",
6
6
  "author": "Robert Blackwell",