react-chess-puzzle-kit 1.0.8 → 1.0.9

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.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { type BoardThemeId } from 'react-chess-core';
2
+ import { type AnalysisEngineOptions, type BoardThemeId } from 'react-chess-core';
3
3
  export type LineTrainSide = 'w' | 'b';
4
4
  /** A line to drill: a start position plus the moves to walk, from one side. */
5
5
  export interface LineSpec {
@@ -38,6 +38,12 @@ export interface LineBoardWithControlsProps {
38
38
  onMove?: (feedback: LineMoveFeedback) => void;
39
39
  /** Omit to use {@link defaultRenderLineControls}. */
40
40
  renderControls?: (props: LineControlsRenderProps) => React.ReactNode;
41
+ /** Optional content above the board (e.g. eval bar), inside play-time engine context. */
42
+ renderAboveBoard?: (props: {
43
+ fen: string;
44
+ }) => React.ReactNode;
45
+ /** Stockfish options when {@link renderAboveBoard} is set. */
46
+ playTimeEngine?: AnalysisEngineOptions;
41
47
  boardWidth?: number;
42
48
  /** Delay before auto-playing each opponent move (ms). */
43
49
  opponentMoveDelayMs?: number;
@@ -49,4 +55,4 @@ export interface LineBoardWithControlsProps {
49
55
  * tactical puzzles. Mount one instance per drill (key it by line) so its
50
56
  * internal state resets between lines.
51
57
  */
52
- export declare const LineBoardWithControls: ({ theme, boardTheme, line, onComplete, onMove, renderControls, boardWidth, opponentMoveDelayMs, }: LineBoardWithControlsProps) => React.JSX.Element;
58
+ export declare const LineBoardWithControls: ({ theme, boardTheme, line, onComplete, onMove, renderControls, renderAboveBoard, playTimeEngine, boardWidth, opponentMoveDelayMs, }: LineBoardWithControlsProps) => React.JSX.Element;
@@ -82,6 +82,10 @@ export interface PuzzleBoardWithControlsProps {
82
82
  renderAnalysisSidebar?: (props: AnalysisSidebarRenderProps) => React.ReactNode;
83
83
  renderAnalysisContainer?: (props: AnalysisContainerRenderProps) => React.ReactNode;
84
84
  renderEngineEvaluation?: (props: EngineEvaluationRenderProps) => React.ReactNode;
85
+ /** Optional content above the board (e.g. eval bar), inside play-time engine context. */
86
+ renderAboveBoard?: (props: {
87
+ fen: string;
88
+ }) => React.ReactNode;
85
89
  /** Optional label below the board (e.g. side to move). */
86
90
  renderBoardCaption?: (props: BoardCaptionRenderProps) => React.ReactNode;
87
91
  /** Optional result feedback shown at the bottom of the controls column. */
@@ -119,4 +123,4 @@ export interface PuzzleBoardWithControlsProps {
119
123
  refutationEngine?: AnalysisEngineOptions;
120
124
  answerArrowColor?: string;
121
125
  }
122
- export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, autoAdvanceOnCompleteDelayMs, showCompletionRecap, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
126
+ export declare const PuzzleBoardWithControls: ({ theme, boardTheme, apiProxy, renderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth, analysisLayout, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete, autoAdvanceOnCompleteAfterIncorrect, autoAdvanceOnCompleteDelayMs, showCompletionRecap, revealAnswerOnIncorrect, showAnswerArrowOnIncorrect, allowRetryOnIncorrect, showRefutationOnIncorrect, autoShowWrongMoves, refutationEngine, answerArrowColor, }: PuzzleBoardWithControlsProps) => React.JSX.Element;
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { useBoardRevision, useCorrectMoveFeedback, useIncorrectMoveFeedback, useMissBoard, HighlightChessboard, DEFAULT_ANSWER_ARROW_COLOR, uciFromDrop, fenAtPlyFromStart, useSolutionLineRecap, lastMoveUciAtPly, isAnalyzableFen, ThemeProvider, AnalysisErrorBoundary, AnalysisBoardCore, AnalysisBoardLayout, AnalysisBoard, PlayTimeEngineProvider, BoardCompleteCheckOverlay, DEFAULT_ANALYSIS_LAYOUT, AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, evaluateExpectedMoveDrop, fenAfterUci, boardSquareHighlightColors, analysisBoardHighlightColors } from 'react-chess-core';
4
4
  export { DEFAULT_ANALYSIS_LAYOUT } from 'react-chess-core';
5
5
  import { Chess } from 'chess.js';
@@ -61,7 +61,7 @@ const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8 w - - 0 1';
61
61
  * visible while the next position loads so layout and perspective do not flicker.
62
62
  */
63
63
  const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, onResumeCorrect, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect = false, autoShowWrongMoves = true, refutationEngine, answerArrowColor = DEFAULT_ANSWER_ARROW_COLOR, positionLocked = false, onMissFeedbackChange, recapBoard = null, }) => {
64
- var _a, _b, _c, _d, _e, _f, _g;
64
+ var _a, _b, _c, _d, _e;
65
65
  const [showAnswerArrow, setShowAnswerArrow] = useState(false);
66
66
  const [incorrectActive, setIncorrectActive] = useState(false);
67
67
  const attemptMissedRef = useRef(false);
@@ -98,16 +98,14 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
98
98
  autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
99
99
  engineOptions: refutationEngine,
100
100
  });
101
- const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
101
+ const missPhase = missBoard.phase;
102
102
  const answerArrowVisible = useRefutation
103
103
  ? incorrectActive && missPhase === 'answer'
104
104
  : showAnswerArrow;
105
- const overlayIncorrectSquare = transientIncorrectSquare !== null && transientIncorrectSquare !== void 0 ? transientIncorrectSquare : (useRefutation && incorrectActive
106
- ? missBoard.missSequence.display.incorrectMoveSquare
107
- : null);
108
- const refutationMoveSquare = useRefutation && incorrectActive
109
- ? missBoard.missSequence.display.refutationMoveSquare
110
- : null;
105
+ const overlayIncorrectSquare = useRefutation && incorrectActive
106
+ ? missBoard.incorrectMoveSquare
107
+ : transientIncorrectSquare;
108
+ const refutationMoveSquare = useRefutation && incorrectActive ? missBoard.refutationMoveSquare : null;
111
109
  useEffect(() => {
112
110
  setShowAnswerArrow(false);
113
111
  setIncorrectActive(false);
@@ -122,14 +120,13 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
122
120
  position,
123
121
  ]);
124
122
  useEffect(() => {
125
- var _a, _b;
126
123
  if (!onMissFeedbackChange) {
127
124
  return;
128
125
  }
129
126
  if (useRefutation && incorrectActive) {
130
127
  onMissFeedbackChange({
131
128
  refutationSan: missBoard.refutation.refutationSan,
132
- phase: (_b = (_a = missBoard.missSequence.sequence) === null || _a === void 0 ? void 0 : _a.phase) !== null && _b !== void 0 ? _b : null,
129
+ phase: missBoard.phase,
133
130
  answerArrowVisible,
134
131
  });
135
132
  return;
@@ -146,7 +143,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
146
143
  }, [
147
144
  answerArrowVisible,
148
145
  incorrectActive,
149
- (_d = missBoard.missSequence.sequence) === null || _d === void 0 ? void 0 : _d.phase,
146
+ missBoard.phase,
150
147
  missBoard.refutation.refutationSan,
151
148
  onMissFeedbackChange,
152
149
  showAnswerArrow,
@@ -186,12 +183,8 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
186
183
  ? recapBoard.lastMoveUci
187
184
  : useRefutation && incorrectActive
188
185
  ? missBoard.lastMoveUci
189
- : ((_e = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _e !== void 0 ? _e : null);
190
- const missLocked = useRefutation &&
191
- incorrectActive &&
192
- (missBoard.boardAnimating ||
193
- missPhase === 'wrong' ||
194
- missPhase === 'refutation');
186
+ : ((_c = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _c !== void 0 ? _c : null);
187
+ const missLocked = useRefutation && incorrectActive && missBoard.inputLocked;
195
188
  const arePiecesDraggable = !isRecapping &&
196
189
  position !== null &&
197
190
  !positionLocked &&
@@ -221,7 +214,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
221
214
  });
222
215
  if (!guess.accepted) {
223
216
  attemptMissedRef.current = true;
224
- showIncorrectMove(useRefutation ? targetSquare : sourceSquare);
217
+ if (!useRefutation) {
218
+ showIncorrectMove(sourceSquare);
219
+ }
225
220
  onFeedback({
226
221
  index: position.getIndex(),
227
222
  guess: { sourceSquare, targetSquare, piece },
@@ -301,7 +296,11 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
301
296
  showCorrectMove(targetSquare, finishCorrectFeedback);
302
297
  return true;
303
298
  };
304
- return hasBoard ? (jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_f = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _f !== void 0 ? _f : ''), hintSquare: isRecapping ? null : ((_g = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _g !== void 0 ? _g : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping ? recapBoard.animationDuration : 0 }, revision)) : null;
299
+ return hasBoard ? (jsx(HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_d = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _d !== void 0 ? _d : ''), hintSquare: isRecapping ? null : ((_e = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _e !== void 0 ? _e : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping
300
+ ? recapBoard.animationDuration
301
+ : useRefutation && incorrectActive
302
+ ? missBoard.animationDuration
303
+ : 0 }, revision)) : null;
305
304
  };
306
305
 
307
306
  const PuzzleBoard = ({ position, onFeedback, incInteractionNum, boardWidth, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, answerArrowColor, }) => (jsx(PuzzlePlaySurface, { position: position, onFeedback: onFeedback, incInteractionNum: incInteractionNum, boardWidth: boardWidth, revealAnswerOnIncorrect: revealAnswerOnIncorrect, showAnswerArrowOnIncorrect: showAnswerArrowOnIncorrect, allowRetryOnIncorrect: allowRetryOnIncorrect, answerArrowColor: answerArrowColor }));
@@ -370,6 +369,7 @@ const usePuzzleCompletionRecap = ({ source, active, onComplete, }) => {
370
369
  segmentStartFen: startFen,
371
370
  setupUci,
372
371
  onComplete,
372
+ completeImmediatelyWhenNoMisses: true,
373
373
  resolveFen,
374
374
  });
375
375
  };
@@ -879,7 +879,7 @@ const buildCompletionRecapSource = (position, missedIndices) => {
879
879
  setupUci: startIndex > 0 ? (_b = movesUci[startIndex - 1]) !== null && _b !== void 0 ? _b : null : null,
880
880
  };
881
881
  };
882
- const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
882
+ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
883
883
  var _a, _b, _c, _d, _e;
884
884
  const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
885
885
  const stackControlsBelow = useStackPuzzleControlsBelow();
@@ -1176,8 +1176,12 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1176
1176
  return;
1177
1177
  }
1178
1178
  completionFlowStartedRef.current = true;
1179
+ if (missedMoveIndices.length === 0) {
1180
+ setCompletionRecapDone(true);
1181
+ return;
1182
+ }
1179
1183
  setCompletionCheckVisible(true);
1180
- }, [loadingNextPuzzle, resultStatus, showCompletionRecap]);
1184
+ }, [loadingNextPuzzle, missedMoveIndices, resultStatus, showCompletionRecap]);
1181
1185
  useEffect(() => {
1182
1186
  if (!completionCheckVisible) {
1183
1187
  return;
@@ -1216,18 +1220,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1216
1220
  const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
1217
1221
  renderAnalysisContainer &&
1218
1222
  (renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
1219
- return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsx(AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsx(AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, children: 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 ||
1220
- completionCheckVisible ||
1221
- isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1222
- ? {
1223
- fen: completionRecap.fen,
1224
- lastMoveUci: completionRecap.lastMoveUci,
1225
- customArrows: completionRecap.customArrows,
1226
- animationDuration: completionRecap.animationDuration,
1227
- }
1228
- : null }) }) }), completionCheckVisible && (jsx(BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1229
- ? 'partial'
1230
- : 'success' }))] }), renderBoardCaption && (jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1223
+ return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: analysisSnapshot ? (jsx(AnalysisErrorBoundary, { onClose: analysis.closeAnalysis, children: useHostAnalysisUi ? (jsx(AnalysisBoardCore, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, boardWidth: resolvedAnalysisBoardWidth, engine: engine, renderMain: renderAnalysisMain !== null && renderAnalysisMain !== void 0 ? renderAnalysisMain : (({ board, sidebar, model }) => (jsx(AnalysisBoardLayout, { layout: analysisLayout, model: model, board: board, sidebar: sidebar }))), renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation !== null && renderEngineEvaluation !== void 0 ? renderEngineEvaluation : (() => null) })) : (jsx(AnalysisBoard, { analysisContext: analysisSnapshot, onClose: analysis.closeAnalysis, theme: theme, layout: analysisLayout, engine: engine, renderMain: renderAnalysisMain, renderSidebar: renderAnalysisSidebar, renderContainer: renderAnalysisContainer, renderEngineEvaluation: renderEngineEvaluation })) })) : (jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsxs("div", { style: puzzleBoardColumnStyle(puzzleBoardWidth, controlsPlacement), children: [jsxs(PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: setupFen }), jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsx("div", { style: puzzleBoardSlotStyle(), children: 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 ||
1224
+ completionCheckVisible ||
1225
+ isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1226
+ ? {
1227
+ fen: completionRecap.fen,
1228
+ lastMoveUci: completionRecap.lastMoveUci,
1229
+ customArrows: completionRecap.customArrows,
1230
+ animationDuration: completionRecap.animationDuration,
1231
+ }
1232
+ : null }) }), completionCheckVisible && (jsx(BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1233
+ ? 'partial'
1234
+ : 'success' }))] })] }), renderBoardCaption && (jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1231
1235
  sideToMove: (_b = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _b !== void 0 ? _b : null,
1232
1236
  playerColor: position
1233
1237
  ? position.getPlayerColor()
@@ -1292,7 +1296,7 @@ const boardOrientationForLine = (side) => side === 'b' ? 'black' : 'white';
1292
1296
  * tactical puzzles. Mount one instance per drill (key it by line) so its
1293
1297
  * internal state resets between lines.
1294
1298
  */
1295
- const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1299
+ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, renderAboveBoard, playTimeEngine, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1296
1300
  const chessRef = useRef(new Chess(line.startFen));
1297
1301
  const perMoveRef = useRef([]);
1298
1302
  const completedRef = useRef(false);
@@ -1417,7 +1421,9 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1417
1421
  const controlsPlacement = stackControlsBelow
1418
1422
  ? 'below'
1419
1423
  : 'beside';
1420
- return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1424
+ const playTimeEnabled = isAnalyzableFen(boardFen);
1425
+ const boardColumn = (jsxs(Fragment, { children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: boardFen }), jsx("div", { style: puzzleBoardSlotStyle(), children: jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) })] }));
1426
+ return (jsx(ThemeProvider, { theme: theme, boardTheme: boardTheme, children: jsxs("div", { style: puzzlePlayRowStyle(controlsPlacement), children: [jsx("div", { style: puzzleBoardColumnStyle(boardWidth, controlsPlacement), children: renderAboveBoard ? (jsx(PlayTimeEngineProvider, { fen: boardFen, enabled: playTimeEnabled, options: playTimeEngine, children: boardColumn })) : (boardColumn) }), jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1421
1427
  trainSide: line.trainSide,
1422
1428
  moveNumber,
1423
1429
  total,
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ const EMPTY_BOARD_FEN = '8/8/8/8/8/8/8/8 w - - 0 1';
62
62
  * visible while the next position loads so layout and perspective do not flicker.
63
63
  */
64
64
  const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth, onResumeCorrect, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect = false, autoShowWrongMoves = true, refutationEngine, answerArrowColor = reactChessCore.DEFAULT_ANSWER_ARROW_COLOR, positionLocked = false, onMissFeedbackChange, recapBoard = null, }) => {
65
- var _a, _b, _c, _d, _e, _f, _g;
65
+ var _a, _b, _c, _d, _e;
66
66
  const [showAnswerArrow, setShowAnswerArrow] = react.useState(false);
67
67
  const [incorrectActive, setIncorrectActive] = react.useState(false);
68
68
  const attemptMissedRef = react.useRef(false);
@@ -99,16 +99,14 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
99
99
  autoShowWrongMoves: useRefutation ? true : autoShowWrongMoves,
100
100
  engineOptions: refutationEngine,
101
101
  });
102
- const missPhase = (_c = missBoard.missSequence.sequence) === null || _c === void 0 ? void 0 : _c.phase;
102
+ const missPhase = missBoard.phase;
103
103
  const answerArrowVisible = useRefutation
104
104
  ? incorrectActive && missPhase === 'answer'
105
105
  : showAnswerArrow;
106
- const overlayIncorrectSquare = transientIncorrectSquare !== null && transientIncorrectSquare !== void 0 ? transientIncorrectSquare : (useRefutation && incorrectActive
107
- ? missBoard.missSequence.display.incorrectMoveSquare
108
- : null);
109
- const refutationMoveSquare = useRefutation && incorrectActive
110
- ? missBoard.missSequence.display.refutationMoveSquare
111
- : null;
106
+ const overlayIncorrectSquare = useRefutation && incorrectActive
107
+ ? missBoard.incorrectMoveSquare
108
+ : transientIncorrectSquare;
109
+ const refutationMoveSquare = useRefutation && incorrectActive ? missBoard.refutationMoveSquare : null;
112
110
  react.useEffect(() => {
113
111
  setShowAnswerArrow(false);
114
112
  setIncorrectActive(false);
@@ -123,14 +121,13 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
123
121
  position,
124
122
  ]);
125
123
  react.useEffect(() => {
126
- var _a, _b;
127
124
  if (!onMissFeedbackChange) {
128
125
  return;
129
126
  }
130
127
  if (useRefutation && incorrectActive) {
131
128
  onMissFeedbackChange({
132
129
  refutationSan: missBoard.refutation.refutationSan,
133
- phase: (_b = (_a = missBoard.missSequence.sequence) === null || _a === void 0 ? void 0 : _a.phase) !== null && _b !== void 0 ? _b : null,
130
+ phase: missBoard.phase,
134
131
  answerArrowVisible,
135
132
  });
136
133
  return;
@@ -147,7 +144,7 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
147
144
  }, [
148
145
  answerArrowVisible,
149
146
  incorrectActive,
150
- (_d = missBoard.missSequence.sequence) === null || _d === void 0 ? void 0 : _d.phase,
147
+ missBoard.phase,
151
148
  missBoard.refutation.refutationSan,
152
149
  onMissFeedbackChange,
153
150
  showAnswerArrow,
@@ -187,12 +184,8 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
187
184
  ? recapBoard.lastMoveUci
188
185
  : useRefutation && incorrectActive
189
186
  ? missBoard.lastMoveUci
190
- : ((_e = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _e !== void 0 ? _e : null);
191
- const missLocked = useRefutation &&
192
- incorrectActive &&
193
- (missBoard.boardAnimating ||
194
- missPhase === 'wrong' ||
195
- missPhase === 'refutation');
187
+ : ((_c = position === null || position === void 0 ? void 0 : position.getLastMoveUci()) !== null && _c !== void 0 ? _c : null);
188
+ const missLocked = useRefutation && incorrectActive && missBoard.inputLocked;
196
189
  const arePiecesDraggable = !isRecapping &&
197
190
  position !== null &&
198
191
  !positionLocked &&
@@ -222,7 +215,9 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
222
215
  });
223
216
  if (!guess.accepted) {
224
217
  attemptMissedRef.current = true;
225
- showIncorrectMove(useRefutation ? targetSquare : sourceSquare);
218
+ if (!useRefutation) {
219
+ showIncorrectMove(sourceSquare);
220
+ }
226
221
  onFeedback({
227
222
  index: position.getIndex(),
228
223
  guess: { sourceSquare, targetSquare, piece },
@@ -302,7 +297,11 @@ const PuzzlePlaySurface = ({ position, onFeedback, incInteractionNum, boardWidth
302
297
  showCorrectMove(targetSquare, finishCorrectFeedback);
303
298
  return true;
304
299
  };
305
- return hasBoard ? (jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_f = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _f !== void 0 ? _f : ''), hintSquare: isRecapping ? null : ((_g = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _g !== void 0 ? _g : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping ? recapBoard.animationDuration : 0 }, revision)) : null;
300
+ return hasBoard ? (jsxRuntime.jsx(reactChessCore.HighlightChessboard, { boardWidth: boardWidth, checkSquare: isRecapping ? '' : ((_d = position === null || position === void 0 ? void 0 : position.getCheckSquare()) !== null && _d !== void 0 ? _d : ''), hintSquare: isRecapping ? null : ((_e = position === null || position === void 0 ? void 0 : position.getHintSquare()) !== null && _e !== void 0 ? _e : null), incorrectMoveSquare: isRecapping ? null : overlayIncorrectSquare, refutationMoveSquare: isRecapping ? null : refutationMoveSquare, correctMoveSquare: isRecapping ? null : correctMoveSquare, customArrows: customArrows, lastMoveUci: lastMoveUci, onPieceDrop: onPieceDrop, position: displayFen, boardOrientation: boardOrientation, arePiecesDraggable: arePiecesDraggable, areArrowsAllowed: false, promotionDialogVariant: "modal", animationDuration: isRecapping
301
+ ? recapBoard.animationDuration
302
+ : useRefutation && incorrectActive
303
+ ? missBoard.animationDuration
304
+ : 0 }, revision)) : null;
306
305
  };
307
306
 
308
307
  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 }));
@@ -371,6 +370,7 @@ const usePuzzleCompletionRecap = ({ source, active, onComplete, }) => {
371
370
  segmentStartFen: startFen,
372
371
  setupUci,
373
372
  onComplete,
373
+ completeImmediatelyWhenNoMisses: true,
374
374
  resolveFen,
375
375
  });
376
376
  };
@@ -880,7 +880,7 @@ const buildCompletionRecapSource = (position, missedIndices) => {
880
880
  setupUci: startIndex > 0 ? (_b = movesUci[startIndex - 1]) !== null && _b !== void 0 ? _b : null : null,
881
881
  };
882
882
  };
883
- 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, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = reactChessCore.AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
883
+ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls = defaultRenderControls, renderAnalysisSidebar, renderAnalysisContainer, renderEngineEvaluation, renderAboveBoard, renderBoardCaption, renderBoardFeedback, puzzleBoardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, analysisLayout = reactChessCore.DEFAULT_ANALYSIS_LAYOUT, analysisBoardWidth, renderAnalysisMain, engine, playTimeEngine, autoAdvanceOnComplete = false, autoAdvanceOnCompleteAfterIncorrect = false, autoAdvanceOnCompleteDelayMs = reactChessCore.AUTO_ADVANCE_ON_COMPLETE_DELAY_MS, showCompletionRecap = false, revealAnswerOnIncorrect = false, showAnswerArrowOnIncorrect = false, allowRetryOnIncorrect = true, showRefutationOnIncorrect, autoShowWrongMoves = true, refutationEngine, answerArrowColor, }) => {
884
884
  var _a, _b, _c, _d, _e;
885
885
  const refutationOnIncorrect = showRefutationOnIncorrect !== null && showRefutationOnIncorrect !== void 0 ? showRefutationOnIncorrect : showAnswerArrowOnIncorrect;
886
886
  const stackControlsBelow = useStackPuzzleControlsBelow();
@@ -1177,8 +1177,12 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1177
1177
  return;
1178
1178
  }
1179
1179
  completionFlowStartedRef.current = true;
1180
+ if (missedMoveIndices.length === 0) {
1181
+ setCompletionRecapDone(true);
1182
+ return;
1183
+ }
1180
1184
  setCompletionCheckVisible(true);
1181
- }, [loadingNextPuzzle, resultStatus, showCompletionRecap]);
1185
+ }, [loadingNextPuzzle, missedMoveIndices, resultStatus, showCompletionRecap]);
1182
1186
  react.useEffect(() => {
1183
1187
  if (!completionCheckVisible) {
1184
1188
  return;
@@ -1217,18 +1221,18 @@ const PuzzleBoardWithControls = ({ theme, boardTheme, apiProxy, renderControls =
1217
1221
  const useHostAnalysisUi = Boolean(renderAnalysisSidebar &&
1218
1222
  renderAnalysisContainer &&
1219
1223
  (renderEngineEvaluation || (engine === null || engine === void 0 ? void 0 : engine.enabled) === false));
1220
- 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.jsxs("div", { style: puzzleBoardSlotWrapperStyle(), children: [jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(reactChessCore.PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, 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 ||
1221
- completionCheckVisible ||
1222
- isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1223
- ? {
1224
- fen: completionRecap.fen,
1225
- lastMoveUci: completionRecap.lastMoveUci,
1226
- customArrows: completionRecap.customArrows,
1227
- animationDuration: completionRecap.animationDuration,
1228
- }
1229
- : null }) }) }), completionCheckVisible && (jsxRuntime.jsx(reactChessCore.BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1230
- ? 'partial'
1231
- : 'success' }))] }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1224
+ 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.jsxs(reactChessCore.PlayTimeEngineProvider, { fen: setupFen, enabled: playTimeEnabled, options: resolvedPlayTimeEngine, children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: setupFen }), jsxRuntime.jsxs("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 ||
1225
+ completionCheckVisible ||
1226
+ isCompletionRecapping, onMissFeedbackChange: setMissFeedback, recapBoard: isCompletionRecapping
1227
+ ? {
1228
+ fen: completionRecap.fen,
1229
+ lastMoveUci: completionRecap.lastMoveUci,
1230
+ customArrows: completionRecap.customArrows,
1231
+ animationDuration: completionRecap.animationDuration,
1232
+ }
1233
+ : null }) }), completionCheckVisible && (jsxRuntime.jsx(reactChessCore.BoardCompleteCheckOverlay, { variant: hasIncorrectAttempt || completedAfterMiss || hintUsed
1234
+ ? 'partial'
1235
+ : 'success' }))] })] }), renderBoardCaption && (jsxRuntime.jsx("div", { style: puzzleBoardCaptionSlotStyle(), children: renderBoardCaption({
1232
1236
  sideToMove: (_b = position === null || position === void 0 ? void 0 : position.getSideToMove()) !== null && _b !== void 0 ? _b : null,
1233
1237
  playerColor: position
1234
1238
  ? position.getPlayerColor()
@@ -1293,7 +1297,7 @@ const boardOrientationForLine = (side) => side === 'b' ? 'black' : 'white';
1293
1297
  * tactical puzzles. Mount one instance per drill (key it by line) so its
1294
1298
  * internal state resets between lines.
1295
1299
  */
1296
- const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1300
+ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, renderControls = defaultRenderLineControls, renderAboveBoard, playTimeEngine, boardWidth = DEFAULT_PUZZLE_BOARD_WIDTH, opponentMoveDelayMs = DEFAULT_OPPONENT_MOVE_DELAY_MS, }) => {
1297
1301
  const chessRef = react.useRef(new chess_js.Chess(line.startFen));
1298
1302
  const perMoveRef = react.useRef([]);
1299
1303
  const completedRef = react.useRef(false);
@@ -1418,7 +1422,9 @@ const LineBoardWithControls = ({ theme, boardTheme, line, onComplete, onMove, re
1418
1422
  const controlsPlacement = stackControlsBelow
1419
1423
  ? 'below'
1420
1424
  : 'beside';
1421
- 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, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) }) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1425
+ const playTimeEnabled = reactChessCore.isAnalyzableFen(boardFen);
1426
+ const boardColumn = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [renderAboveBoard === null || renderAboveBoard === void 0 ? void 0 : renderAboveBoard({ fen: boardFen }), jsxRuntime.jsx("div", { style: puzzleBoardSlotStyle(), children: jsxRuntime.jsx(LineBoard, { fen: boardFen, orientation: orientation, trainSide: line.trainSide, draggable: isUserTurn, correctMoveSquare: correctMoveSquare, incorrectMoveSquare: incorrectMoveSquare, lastMoveUci: lastMoveUci, onPieceDrop: handleDrop, boardWidth: boardWidth }) })] }));
1427
+ 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: renderAboveBoard ? (jsxRuntime.jsx(reactChessCore.PlayTimeEngineProvider, { fen: boardFen, enabled: playTimeEnabled, options: playTimeEngine, children: boardColumn })) : (boardColumn) }), jsxRuntime.jsx("div", { style: puzzleControlsSlotStyle(controlsPlacement), children: renderControls({
1422
1428
  trainSide: line.trainSide,
1423
1429
  moveNumber,
1424
1430
  total,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-puzzle-kit",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "React chess puzzle kit: play, controls, analysis, and browser Stockfish for endchess.training and other apps",
5
5
  "license": "MIT",
6
6
  "author": "Robert Blackwell",
@@ -76,7 +76,7 @@
76
76
  "jest": "^29.7.0",
77
77
  "jest-environment-jsdom": "^29.7.0",
78
78
  "react": "^18.3.1",
79
- "react-chess-core": "^0.1.8",
79
+ "react-chess-core": "^0.1.12",
80
80
  "react-chessboard": "^4.7.1",
81
81
  "react-dom": "^18.3.1",
82
82
  "storybook": "^8.2.9",