react-chess-puzzle-kit 1.0.1 → 1.0.3

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
@@ -66,7 +66,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
66
66
  var _a, _b, _c, _d, _e, _f, _g;
67
67
  const [showAnswerArrow, setShowAnswerArrow] = react.useState(false);
68
68
  const [incorrectActive, setIncorrectActive] = react.useState(false);
69
+ const attemptMissedRef = react.useRef(false);
69
70
  const { revision, bumpRevision } = reactChessCore.useBoardRevision();
71
+ const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, } = reactChessCore.useCorrectMoveFeedback();
70
72
  const boardOrientationRef = react.useRef('white');
71
73
  const boardFenRef = react.useRef(EMPTY_BOARD_FEN);
72
74
  const notifyHost = () => {
@@ -92,7 +94,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
92
94
  expectedUci: expectedUci || null,
93
95
  positionFen,
94
96
  answerArrowColor,
95
- autoShowWrongMoves,
97
+ // Refutation + answer-arrow flows must run the full wrong→refutation→answer
98
+ // sequence; the replay "retry without arrow" setting does not apply here.
99
+ autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
96
100
  engineOptions: refutationEngine,
97
101
  });
98
102
  const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
@@ -102,8 +106,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
102
106
  react.useEffect(() => {
103
107
  setShowAnswerArrow(false);
104
108
  setIncorrectActive(false);
109
+ attemptMissedRef.current = false;
110
+ clearCorrectMoveFeedback();
105
111
  onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
106
- }, [onMissFeedbackChange, position]);
112
+ }, [clearCorrectMoveFeedback, onMissFeedbackChange, position]);
107
113
  react.useEffect(() => {
108
114
  var _a, _b;
109
115
  if (!onMissFeedbackChange) {
@@ -161,7 +167,10 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
161
167
  (missBoard.boardAnimating ||
162
168
  missPhase === 'wrong' ||
163
169
  missPhase === 'refutation');
164
- const arePiecesDraggable = position !== null && !positionLocked && !missLocked;
170
+ const arePiecesDraggable = position !== null &&
171
+ !positionLocked &&
172
+ !missLocked &&
173
+ correctMoveSquare === null;
165
174
  const onPieceDrop = (sourceSquare, targetSquare, piece) => {
166
175
  if (!position || positionLocked || position.isSolutionRevealed()) {
167
176
  return false;
@@ -183,6 +192,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
183
192
  recordIfIncorrect: !(answerArrowVisible && !allowRetryOnIncorrect),
184
193
  });
185
194
  if (!guess.accepted) {
195
+ attemptMissedRef.current = true;
186
196
  onFeedback({
187
197
  index: position.getIndex(),
188
198
  guess: { sourceSquare, targetSquare, piece },
@@ -225,38 +235,47 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
225
235
  setIncorrectActive(false);
226
236
  missBoard.missSequence.clearSequence();
227
237
  onMissFeedbackChange === null || onMissFeedbackChange === void 0 ? void 0 : onMissFeedbackChange(null);
228
- onFeedback({
238
+ clearCorrectMoveFeedback();
239
+ const assistedByAnswerArrow = answerArrowVisible && attemptMissedRef.current;
240
+ const guessPayload = {
229
241
  index: position.getIndex(),
230
242
  guess: { sourceSquare, targetSquare, piece },
231
- isCorrect: true,
232
- isFinished: guess.finished,
233
- });
234
- notifyHost();
235
- setTimeout(() => {
236
- position.resetInteractions();
237
- notifyHost();
238
- }, 500);
239
- if (position.isAlternativeCheckmate()) {
240
- notifyHost();
241
- return true;
243
+ };
244
+ if (assistedByAnswerArrow) {
245
+ // Miss feedback for this ply is already saved; dragging along the answer
246
+ // arrow only continues the line — it must not count as a clean solve.
247
+ if (guess.finished) {
248
+ onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: false, isFinished: true }));
249
+ }
250
+ }
251
+ else {
252
+ onFeedback(Object.assign(Object.assign({}, guessPayload), { isCorrect: true, isFinished: guess.finished }));
242
253
  }
243
254
  position.next();
255
+ boardFenRef.current = position.fen();
244
256
  notifyHost();
245
- if (position.hasResumeConfig()) {
246
- onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
247
- return true;
248
- }
249
- setTimeout(() => {
257
+ const finishCorrectFeedback = () => {
258
+ position.resetInteractions();
259
+ notifyHost();
260
+ if (position.isAlternativeCheckmate()) {
261
+ return;
262
+ }
263
+ if (position.hasResumeConfig()) {
264
+ onResumeCorrect === null || onResumeCorrect === void 0 ? void 0 : onResumeCorrect(position);
265
+ return;
266
+ }
250
267
  if (!position.isFinished()) {
251
268
  position.next();
269
+ boardFenRef.current = position.fen();
252
270
  }
253
271
  notifyHost();
254
- }, 500);
272
+ };
273
+ showCorrectMove(targetSquare, finishCorrectFeedback);
255
274
  return true;
256
275
  };
257
276
  return (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: hasBoard ? (jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: (_e = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _e !== void 0 ? _e : '', hintSquare: (_f = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _f !== void 0 ? _f : null, incorrectMoveSquare: showAnswerArrowOnIncorrect
258
277
  ? null
259
- : ((_g = position === null || position === void 0 ? void 0 : position.getIncorrectMoveSquare()) !== null && _g !== void 0 ? _g : null), customArrows: customArrows, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: 0 }, revision)) : null }));
278
+ : ((_g = position === null || position === void 0 ? void 0 : position.getIncorrectMoveSquare()) !== null && _g !== void 0 ? _g : null), correctMoveSquare: correctMoveSquare, customArrows: customArrows, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: 0 }, revision)) : null }));
260
279
  };
261
280
 
262
281
  const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsxRuntime.jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
@@ -721,7 +740,7 @@ const puzzlePositionFromFetch = (fen, moves, resume) => {
721
740
  const AUTO_ADVANCE_ON_COMPLETE_DELAY_MS = 700;
722
741
  const SOLUTION_STEP_MS = 500;
723
742
  const RESUME_AUTO_STEP_MS = 500;
724
- const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
743
+ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
725
744
  var _a, _b, _c, _d;
726
745
  const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
727
746
  const stackControlsBelow = useStackPuzzleControlsBelow();
@@ -733,6 +752,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
733
752
  const [loadingNextPuzzle, setLoadingNextPuzzle] = react.useState(true);
734
753
  const [puzzleNum, setPuzzleNum] = react.useState(0);
735
754
  const [hasIncorrectAttempt, setHasIncorrectAttempt] = react.useState(false);
755
+ const [hintUsed, setHintUsed] = react.useState(false);
736
756
  const [puzzleComplete, setPuzzleComplete] = react.useState(false);
737
757
  const [completedAfterMiss, setCompletedAfterMiss] = react.useState(false);
738
758
  const [missFeedback, setMissFeedback] = react.useState(null);
@@ -758,6 +778,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
758
778
  let cancelled = false;
759
779
  setLoadingNextPuzzle(true);
760
780
  setHasIncorrectAttempt(false);
781
+ setHintUsed(false);
761
782
  setPuzzleComplete(false);
762
783
  setCompletedAfterMiss(false);
763
784
  setMissFeedback(null);
@@ -794,12 +815,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
794
815
  const incorrectThisFeedback = feedbackData.hintRequested ||
795
816
  feedbackData.solutionShown ||
796
817
  feedbackData.isCorrect === false;
818
+ if (feedbackData.hintRequested) {
819
+ setHintUsed(true);
820
+ }
797
821
  if (incorrectThisFeedback) {
798
822
  setHasIncorrectAttempt(true);
799
823
  }
800
824
  if (feedbackData.isFinished) {
801
825
  setPuzzleComplete(true);
802
- setCompletedAfterMiss(hasIncorrectAttempt || incorrectThisFeedback);
826
+ setCompletedAfterMiss((prev) => prev ||
827
+ hasIncorrectAttempt ||
828
+ incorrectThisFeedback ||
829
+ feedbackData.hintRequested === true);
803
830
  }
804
831
  onFeedback(feedbackData);
805
832
  };
@@ -1003,10 +1030,11 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1003
1030
  };
1004
1031
  const analysis = usePuzzleAnalysis(position, resultStatus, puzzleNum);
1005
1032
  const analysisSnapshot = analysis.isOpen && analysis.snapshot ? analysis.snapshot : null;
1033
+ const resolvedAnalysisBoardWidth = analysisBoardWidth !== null && analysisBoardWidth !== void 0 ? analysisBoardWidth : analysisLayout.boardWidth;
1006
1034
  const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
1007
1035
  renderAnalysisContainer &&
1008
1036
  (renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
1009
- return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: analysisLayout.boardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsxRuntime.jsx(reactChessCore.AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsxRuntime.jsx(reactChessCore.AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotWrapperStyle(), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle, onMissFeedbackChange: setMissFeedback }) }) }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1037
+ return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsxRuntime.jsx(reactChessCore.AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsxRuntime.jsx(reactChessCore.AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsxRuntime.jsx(reactChessCore.AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsxRuntime.jsx(reactChessCore.AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotWrapperStyle(), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(PuzzlePlaySurface, { position: position, boardWidth: puzzleBoardWidth, onFeedback: handleFeedback, incInteractionNum: incInteractionNum, onResumeCorrect: runResumeAutoAdvance, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, showRefutationOnIncorrect: refutationOnIncorrect, autoShowWrongMoves: autoShowWrongMoves, refutationEngine: refutationEngine !== null && refutationEngine !== void 0 ? refutationEngine : engine, answerArrowColor: answerArrowColor, positionLocked: loadingNextPuzzle, onMissFeedbackChange: setMissFeedback }) }) }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1010
1038
  sideToMove: (_a = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _a !== void 0 ? _a : null,
1011
1039
  playerColor: position
1012
1040
  ? position.getPlayerColor()
@@ -1018,6 +1046,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1018
1046
  missPhase: (_c = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase) !== null && _c !== void 0 ? _c : null,
1019
1047
  answerArrowVisible: (_d = missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.answerArrowVisible) !== null && _d !== void 0 ? _d : false,
1020
1048
  completedAfterMiss,
1049
+ hintUsed,
1021
1050
  }) }))] }), jsxRuntime.jsxs("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: [renderControls(handleHintRequest, handleShowSolution, handleNextPuzzle, resultStatus, {
1022
1051
  visible: analysis.canOpen,
1023
1052
  openAnalysis: analysis.openAnalysis,
@@ -1026,6 +1055,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1026
1055
  cleanSolve: !hasIncorrectAttempt,
1027
1056
  refutationSan: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.refutationSan,
1028
1057
  missPhase: missFeedback === null || missFeedback === void 0 ? void 0 : missFeedback.phase,
1058
+ hintUsed,
1029
1059
  }) }))] })] })) }));
1030
1060
  };
1031
1061
 
@@ -1034,7 +1064,7 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1034
1064
  * side's pieces be dragged. Move validation and sequencing live in
1035
1065
  * {@link LineBoardWithControls}.
1036
1066
  */
1037
- const LineBoard = ({ fen, orientation, trainSide, draggable, onPieceDrop, boardWidth, }) => (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, position: fen, boardOrientation: orientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => piece[0] === trainSide, onPieceDrop: (source, target, piece) => onPieceDrop(source, target, piece), autoPromoteToQueen: true, areArrowsAllowed: false, customBoardStyle: { borderRadius: 4 } }) }));
1067
+ const LineBoard = ({ fen, orientation, trainSide, draggable, correctMoveSquare = null, onPieceDrop, boardWidth, }) => (jsxRuntime.jsx(reactChessCore.ChessboardDnDProvider, { children: jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: "", hintSquare: null, incorrectMoveSquare: null, correctMoveSquare: correctMoveSquare, position: fen, boardOrientation: orientation, arePiecesDraggable: draggable, isDraggablePiece: ({ piece }) => piece[0] === trainSide, onPieceDrop: (source, target, piece) => onPieceDrop(source, target, piece), autoPromoteToQueen: true, areArrowsAllowed: false, customBoardStyle: { borderRadius: 4 } }) }));
1038
1068
 
1039
1069
  /** Library default line-drill status controls (unstyled). */
1040
1070
  const DefaultLineControls = ({ moveNumber, total, finished, isUserTurn, feedback, }) => (jsxRuntime.jsxs("div", { style: rowStyle, children: [jsxRuntime.jsx("span", { style: statusStyle, children: finished ? 'Line complete' : `Move ${moveNumber} of ${total}` }), feedback && !finished && (jsxRuntime.jsx("span", { style: Object.assign(Object.assign({}, statusStyle), { color: feedback.isCorrect ? '#2e7d32' : '#c62828' }), children: feedback.isCorrect
@@ -1081,6 +1111,8 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1081
1111
  const [currentIndex, setCurrentIndex] = react.useState(0);
1082
1112
  const [finished, setFinished] = react.useState(false);
1083
1113
  const [feedback, setFeedback] = react.useState(null);
1114
+ const [displayFen, setDisplayFen] = react.useState(null);
1115
+ const { correctMoveSquare, showCorrectMove, clearCorrectMoveFeedback, isShowingCorrectMove, } = reactChessCore.useCorrectMoveFeedback();
1084
1116
  const total = line.movesUci.length;
1085
1117
  const orientation = boardOrientationForLine(line.trainSide);
1086
1118
  const applyMove = react.useCallback((index) => {
@@ -1105,7 +1137,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1105
1137
  }, [line.movesUci]);
1106
1138
  // Auto-play opponent moves and detect the end of the line.
1107
1139
  react.useEffect(() => {
1108
- if (finished) {
1140
+ if (finished || isShowingCorrectMove) {
1109
1141
  return;
1110
1142
  }
1111
1143
  if (currentIndex >= total) {
@@ -1124,6 +1156,7 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1124
1156
  line.trainSide,
1125
1157
  applyMove,
1126
1158
  opponentMoveDelayMs,
1159
+ isShowingCorrectMove,
1127
1160
  ]);
1128
1161
  // Emit the completion event exactly once.
1129
1162
  react.useEffect(() => {
@@ -1135,15 +1168,16 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1135
1168
  }, [finished]);
1136
1169
  const handleDrop = (source, target, piece) => {
1137
1170
  var _a, _b, _c;
1138
- if (finished) {
1171
+ if (finished || isShowingCorrectMove) {
1139
1172
  return false;
1140
1173
  }
1141
- if (turnFromFen(chessRef.current.fen()) !== line.trainSide) {
1174
+ const setupFen = displayFen !== null && displayFen !== void 0 ? displayFen : chessRef.current.fen();
1175
+ if (turnFromFen(setupFen) !== line.trainSide) {
1142
1176
  return false;
1143
1177
  }
1144
1178
  const index = currentIndex;
1145
1179
  const expected = line.movesUci[index];
1146
- const dropResult = reactChessCore.evaluateExpectedMoveDrop(chessRef.current.fen(), source, target, piece, expected, true);
1180
+ const dropResult = reactChessCore.evaluateExpectedMoveDrop(setupFen, source, target, piece, expected, true);
1147
1181
  if (dropResult.kind === 'illegal' || dropResult.kind === 'ignored') {
1148
1182
  return false;
1149
1183
  }
@@ -1153,16 +1187,31 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1153
1187
  const moveFeedback = { index, isCorrect, expectedSan };
1154
1188
  setFeedback(moveFeedback);
1155
1189
  (_c = onMoveRef.current) === null || _c === void 0 ? void 0 : _c.call(onMoveRef, moveFeedback);
1156
- applyMove(index);
1190
+ if (isCorrect) {
1191
+ const nextFen = reactChessCore.fenAfterUci(setupFen, dropResult.uci);
1192
+ if (nextFen) {
1193
+ setDisplayFen(nextFen);
1194
+ }
1195
+ showCorrectMove(target, () => {
1196
+ setDisplayFen(null);
1197
+ setFeedback(null);
1198
+ clearCorrectMoveFeedback();
1199
+ applyMove(index);
1200
+ });
1201
+ }
1157
1202
  return isCorrect;
1158
1203
  };
1204
+ const boardFen = displayFen !== null && displayFen !== void 0 ? displayFen : fen;
1159
1205
  const moveNumber = Math.min(currentIndex + 1, total);
1160
- const isUserTurn = !finished && turnFromFen(fen) === line.trainSide && currentIndex < total;
1206
+ const isUserTurn = !finished &&
1207
+ !isShowingCorrectMove &&
1208
+ turnFromFen(boardFen) === line.trainSide &&
1209
+ currentIndex < total;
1161
1210
  const stackControlsBelow = useStackPuzzleControlsBelow();
1162
1211
  const controlsPlacement = stackControlsBelow
1163
1212
  ? 'below'
1164
1213
  : 'beside';
1165
- return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: fen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1214
+ return (jsxRuntime.jsx(reactChessCore.ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxRuntime.jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxRuntime.jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1166
1215
  trainSide: line.trainSide,
1167
1216
  moveNumber,
1168
1217
  total,
package/package.json CHANGED
@@ -1,87 +1,87 @@
1
- {
2
- "name": "react-chess-puzzle-kit",
3
- "version": "1.0.1",
4
- "description": "React chess puzzle kit: play, controls, analysis, and browser Stockfish for endchess.training and other apps",
5
- "license": "MIT",
6
- "author": "Robert Blackwell",
7
- "main": "dist/index.js",
8
- "module": "dist/index.esm.js",
9
- "types": "dist/index.d.ts",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/reblackwell3/react-chess-puzzle-kit.git"
13
- },
14
- "bugs": {
15
- "url": "https://github.com/reblackwell3/react-chess-puzzle-kit/issues"
16
- },
17
- "homepage": "https://github.com/reblackwell3/react-chess-puzzle-kit#readme",
18
- "keywords": [
19
- "react",
20
- "chess",
21
- "chess.js",
22
- "chess puzzle",
23
- "puzzle",
24
- "analysis",
25
- "stockfish",
26
- "react-chessboard",
27
- "chessboard",
28
- "drag and drop"
29
- ],
30
- "files": [
31
- "dist"
32
- ],
33
- "scripts": {
34
- "build": "rollup -c",
35
- "copy:stockfish": "node scripts/copy-stockfish.mjs",
36
- "prepare": "rollup -c",
37
- "prestorybook": "npm run copy:stockfish",
38
- "test": "jest --config jest.config.cjs",
39
- "storybook": "storybook dev -p 6006",
40
- "build-storybook": "storybook build",
41
- "serve-storybook": "serve storybook-static",
42
- "prepublishOnly": "npm run build"
43
- },
44
- "dependencies": {
45
- "chess.js": "^1.0.0-beta.8",
46
- "rollup": "^4.22.2",
47
- "rollup-plugin-peer-deps-external": "^2.2.4",
48
- "rollup-plugin-typescript2": "^0.36.0",
49
- "typescript": "^5.6.2"
50
- },
51
- "peerDependencies": {
52
- "chess.js": "^1.0.0-beta.8",
53
- "react": "^18.3.1",
54
- "react-chess-core": "^0.1.1",
55
- "react-chessboard": "^4.7.1"
56
- },
57
- "devDependencies": {
58
- "@chromatic-com/storybook": "^1.9.0",
59
- "@rollup/plugin-commonjs": "^26.0.1",
60
- "@rollup/plugin-node-resolve": "^15.2.3",
61
- "@rollup/plugin-terser": "^0.4.4",
62
- "@storybook/addon-essentials": "^8.2.9",
63
- "@storybook/addon-interactions": "^8.2.9",
64
- "@storybook/addon-links": "^8.2.9",
65
- "@storybook/addon-onboarding": "^8.2.9",
66
- "@storybook/blocks": "^8.2.9",
67
- "@storybook/preset-typescript": "^3.0.0",
68
- "@storybook/react": "^8.2.9",
69
- "@storybook/react-vite": "^8.2.9",
70
- "@storybook/test": "^8.2.9",
71
- "@types/jest": "^29.5.12",
72
- "@types/react": "^18.3.12",
73
- "@types/react-dom": "^18.3.1",
74
- "chess.js": "^1.0.0-beta.8",
75
- "jest": "^29.7.0",
76
- "react": "^18.3.1",
77
- "react-chess-core": "^0.1.1",
78
- "react-chessboard": "^4.7.1",
79
- "storybook": "^8.2.9",
80
- "ts-jest": "^29.2.4",
81
- "vite": "^5.4.11",
82
- "vite-tsconfig-paths": "^5.0.1"
83
- },
84
- "optionalDependencies": {
85
- "stockfish": "^18.0.7"
86
- }
87
- }
1
+ {
2
+ "name": "react-chess-puzzle-kit",
3
+ "version": "1.0.3",
4
+ "description": "React chess puzzle kit: play, controls, analysis, and browser Stockfish for endchess.training and other apps",
5
+ "license": "MIT",
6
+ "author": "Robert Blackwell",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.esm.js",
9
+ "types": "dist/index.d.ts",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/reblackwell3/react-chess-puzzle-kit.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/reblackwell3/react-chess-puzzle-kit/issues"
16
+ },
17
+ "homepage": "https://github.com/reblackwell3/react-chess-puzzle-kit#readme",
18
+ "keywords": [
19
+ "react",
20
+ "chess",
21
+ "chess.js",
22
+ "chess puzzle",
23
+ "puzzle",
24
+ "analysis",
25
+ "stockfish",
26
+ "react-chessboard",
27
+ "chessboard",
28
+ "drag and drop"
29
+ ],
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "rollup -c",
35
+ "copy:stockfish": "node scripts/copy-stockfish.mjs",
36
+ "prepare": "rollup -c",
37
+ "prestorybook": "npm run copy:stockfish",
38
+ "test": "jest --config jest.config.cjs",
39
+ "storybook": "storybook dev -p 6006",
40
+ "build-storybook": "storybook build",
41
+ "serve-storybook": "serve storybook-static",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "dependencies": {
45
+ "chess.js": "^1.0.0-beta.8",
46
+ "rollup": "^4.22.2",
47
+ "rollup-plugin-peer-deps-external": "^2.2.4",
48
+ "rollup-plugin-typescript2": "^0.36.0",
49
+ "typescript": "^5.6.2"
50
+ },
51
+ "peerDependencies": {
52
+ "chess.js": "^1.0.0-beta.8",
53
+ "react": "^18.3.1",
54
+ "react-chess-core": "^0.1.1",
55
+ "react-chessboard": "^4.7.1"
56
+ },
57
+ "devDependencies": {
58
+ "@chromatic-com/storybook": "^1.9.0",
59
+ "@rollup/plugin-commonjs": "^26.0.1",
60
+ "@rollup/plugin-node-resolve": "^15.2.3",
61
+ "@rollup/plugin-terser": "^0.4.4",
62
+ "@storybook/addon-essentials": "^8.2.9",
63
+ "@storybook/addon-interactions": "^8.2.9",
64
+ "@storybook/addon-links": "^8.2.9",
65
+ "@storybook/addon-onboarding": "^8.2.9",
66
+ "@storybook/blocks": "^8.2.9",
67
+ "@storybook/preset-typescript": "^3.0.0",
68
+ "@storybook/react": "^8.2.9",
69
+ "@storybook/react-vite": "^8.2.9",
70
+ "@storybook/test": "^8.2.9",
71
+ "@types/jest": "^29.5.12",
72
+ "@types/react": "^18.3.12",
73
+ "@types/react-dom": "^18.3.1",
74
+ "chess.js": "^1.0.0-beta.8",
75
+ "jest": "^29.7.0",
76
+ "react": "^18.3.1",
77
+ "react-chess-core": "^0.1.1",
78
+ "react-chessboard": "^4.7.1",
79
+ "storybook": "^8.2.9",
80
+ "ts-jest": "^29.2.4",
81
+ "vite": "^5.4.11",
82
+ "vite-tsconfig-paths": "^5.0.1"
83
+ },
84
+ "optionalDependencies": {
85
+ "stockfish": "^18.0.7"
86
+ }
87
+ }