react-chess-core 0.1.3 → 0.1.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.
@@ -49,6 +49,7 @@ export declare class AnalysisPosition {
49
49
  from: string;
50
50
  to: string;
51
51
  } | null;
52
+ getLastMoveUci(): string | null;
52
53
  fen(): string;
53
54
  getCheckSquare(): string;
54
55
  }
@@ -25,6 +25,7 @@ export type AnalysisBoardModel = {
25
25
  from: string;
26
26
  to: string;
27
27
  } | null;
28
+ lastMoveUci: string | null;
28
29
  checkSquare: string | null;
29
30
  onSelectPly: (ply: number) => void;
30
31
  onSelectHistoryRow: (row: AnalysisHistoryRow) => void;
@@ -4,8 +4,12 @@ export interface HighlightChessboardProps {
4
4
  incorrectMoveSquare: string | null;
5
5
  /** Destination square of the last correct training move — shows a green check. */
6
6
  correctMoveSquare?: string | null;
7
+ /** UCI of the move that led to the current position (shows a last-move arrow). */
8
+ lastMoveUci?: string | null;
9
+ /** Override the default last-move arrow color. */
10
+ lastMoveArrowColor?: string;
7
11
  /** Enable click-to-move when `onPieceDrop` is provided. Defaults to true. */
8
12
  clickToMove?: boolean;
9
13
  [key: string]: any;
10
14
  }
11
- export declare const HighlightChessboard: ({ checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare, clickToMove, customSquareStyles: extraSquareStyles, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp, ...props }: HighlightChessboardProps) => import("react").JSX.Element;
15
+ export declare const HighlightChessboard: ({ checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare, lastMoveUci, lastMoveArrowColor, clickToMove, customSquareStyles: extraSquareStyles, customArrows, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp, ...props }: HighlightChessboardProps) => import("react").JSX.Element;
@@ -4,3 +4,4 @@ export * from './ChessboardDnDProvider';
4
4
  export * from './HighlightChessboard';
5
5
  export * from './CorrectMoveCheckBadge';
6
6
  export * from './boardSquareHighlightColors';
7
+ export * from './lastMoveArrow';
@@ -0,0 +1,8 @@
1
+ export type ChessboardArrow = [string, string, string];
2
+ /** Subtle green arrow visible on light and dark boards (Lichess-style). */
3
+ export declare const DEFAULT_LAST_MOVE_ARROW_COLOR = "rgba(155, 199, 0, 0.85)";
4
+ export declare const uciToArrow: (uci: string, color?: string) => ChessboardArrow;
5
+ export declare const lastMoveArrowFromUci: (uci: string | null | undefined, color?: string) => ChessboardArrow[];
6
+ /** UCI of the move that produced the position at {@link plyIndex}. */
7
+ export declare const lastMoveUciAtPly: (movesUci: readonly string[], plyIndex: number) => string | null;
8
+ export declare const mergeCustomArrowsWithLastMove: (customArrows: ChessboardArrow[] | undefined, lastMoveUci: string | null | undefined, lastMoveArrowColor?: string) => ChessboardArrow[];
@@ -7,6 +7,7 @@ export type MissSequenceState = {
7
7
  export type MissDisplay = {
8
8
  fen: string | null;
9
9
  arrows: [string, string, string][];
10
+ lastMoveUci: string | null;
10
11
  animating: boolean;
11
12
  };
12
13
  export declare const MISS_WRONG_PAUSE_MS = 450;
@@ -19,6 +19,7 @@ export declare function useMissBoard({ feedback, expectedUci, positionFen, answe
19
19
  customArrows: [string, string, string][];
20
20
  boardPosition: string;
21
21
  boardAnimating: boolean;
22
+ lastMoveUci: string | null;
22
23
  wrapDropHandler: (onDrop: (source: string, target: string, piece: string) => boolean, { enabled, dropFen, expectedMoveUci, }: {
23
24
  enabled: boolean;
24
25
  dropFen?: string;
package/dist/index.esm.js CHANGED
@@ -1781,6 +1781,31 @@ function createFeedbackSquareRenderer(correctMoveSquare) {
1781
1781
  };
1782
1782
  }
1783
1783
 
1784
+ /** Subtle green arrow visible on light and dark boards (Lichess-style). */
1785
+ const DEFAULT_LAST_MOVE_ARROW_COLOR = 'rgba(155, 199, 0, 0.85)';
1786
+ const uciToArrow = (uci, color = DEFAULT_LAST_MOVE_ARROW_COLOR) => [uci.slice(0, 2), uci.slice(2, 4), color];
1787
+ const lastMoveArrowFromUci = (uci, color = DEFAULT_LAST_MOVE_ARROW_COLOR) => {
1788
+ if (!uci || uci.length < 4) {
1789
+ return [];
1790
+ }
1791
+ return [uciToArrow(uci, color)];
1792
+ };
1793
+ /** UCI of the move that produced the position at {@link plyIndex}. */
1794
+ const lastMoveUciAtPly = (movesUci, plyIndex) => {
1795
+ var _a;
1796
+ if (plyIndex <= 0) {
1797
+ return null;
1798
+ }
1799
+ return (_a = movesUci[plyIndex - 1]) !== null && _a !== void 0 ? _a : null;
1800
+ };
1801
+ const mergeCustomArrowsWithLastMove = (customArrows, lastMoveUci, lastMoveArrowColor) => {
1802
+ const lastMove = lastMoveArrowFromUci(lastMoveUci, lastMoveArrowColor);
1803
+ if (!lastMove.length) {
1804
+ return customArrows !== null && customArrows !== void 0 ? customArrows : [];
1805
+ }
1806
+ return [...lastMove, ...(customArrows !== null && customArrows !== void 0 ? customArrows : [])];
1807
+ };
1808
+
1784
1809
  function defaultPromotionCheck(sourceSquare, targetSquare, piece) {
1785
1810
  return (((piece === 'wP' && sourceSquare[1] === '7' && targetSquare[1] === '8') ||
1786
1811
  (piece === 'bP' && sourceSquare[1] === '2' && targetSquare[1] === '1')) &&
@@ -1953,7 +1978,7 @@ const getFeedbackHighlighting = (hintSquare, incorrectMoveSquare) => {
1953
1978
  return styles;
1954
1979
  };
1955
1980
  const HighlightChessboard = (_a) => {
1956
- var { checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare = null, clickToMove, customSquareStyles: extraSquareStyles, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp } = _a, props = __rest(_a, ["checkSquare", "hintSquare", "incorrectMoveSquare", "correctMoveSquare", "clickToMove", "customSquareStyles", "customBoardStyle", "onPieceDrop", "position", "arePiecesDraggable", "autoPromoteToQueen", "isDraggablePiece", "onPromotionCheck", "onSquareClick", "onPromotionPieceSelect", "onPieceDragBegin", "showPromotionDialog", "promotionToSquare"]);
1981
+ var { checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare = null, lastMoveUci = null, lastMoveArrowColor, clickToMove, customSquareStyles: extraSquareStyles, customArrows, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp } = _a, props = __rest(_a, ["checkSquare", "hintSquare", "incorrectMoveSquare", "correctMoveSquare", "lastMoveUci", "lastMoveArrowColor", "clickToMove", "customSquareStyles", "customArrows", "customBoardStyle", "onPieceDrop", "position", "arePiecesDraggable", "autoPromoteToQueen", "isDraggablePiece", "onPromotionCheck", "onSquareClick", "onPromotionPieceSelect", "onPieceDragBegin", "showPromotionDialog", "promotionToSquare"]);
1957
1982
  const { customDarkSquareStyle, customLightSquareStyle } = useChessboardTheme();
1958
1983
  const clickToMoveEnabled = clickToMove !== false && typeof onPieceDrop === 'function';
1959
1984
  const { clickSquareStyles, handleSquareClick, handlePromotionPieceSelect, handlePieceDragBegin, showPromotionDialog: clickPromotionDialog, promotionToSquare: clickPromotionToSquare, } = useClickToMove({
@@ -1974,6 +1999,7 @@ const HighlightChessboard = (_a) => {
1974
1999
  const customSquare = useMemo(() => correctMoveSquare
1975
2000
  ? createFeedbackSquareRenderer(correctMoveSquare)
1976
2001
  : undefined, [correctMoveSquare]);
2002
+ const mergedCustomArrows = useMemo(() => mergeCustomArrowsWithLastMove(customArrows, lastMoveUci, lastMoveArrowColor), [customArrows, lastMoveArrowColor, lastMoveUci]);
1977
2003
  const promotionControlProps = clickPromotionDialog
1978
2004
  ? {
1979
2005
  showPromotionDialog: true,
@@ -1986,7 +2012,7 @@ const HighlightChessboard = (_a) => {
1986
2012
  promotionToSquare: promotionToSquareProp,
1987
2013
  }
1988
2014
  : {};
1989
- return (jsx(Chessboard, Object.assign({ customDarkSquareStyle: customDarkSquareStyle, customLightSquareStyle: customLightSquareStyle, customSquareStyles: customSquareStyles, customSquare: customSquare, customBoardStyle: Object.assign(Object.assign({}, nonSelectableBoardStyle), customBoardStyle), position: position, arePiecesDraggable: arePiecesDraggable, autoPromoteToQueen: autoPromoteToQueen, isDraggablePiece: isDraggablePiece, onPromotionCheck: onPromotionCheck, onPieceDrop: onPieceDrop, onSquareClick: clickToMoveEnabled ? handleSquareClick : onSquareClick, onPromotionPieceSelect: clickToMoveEnabled ? handlePromotionPieceSelect : onPromotionPieceSelect, onPieceDragBegin: clickToMoveEnabled ? handlePieceDragBegin : onPieceDragBegin }, promotionControlProps, props)));
2015
+ return (jsx(Chessboard, Object.assign({ customDarkSquareStyle: customDarkSquareStyle, customLightSquareStyle: customLightSquareStyle, customSquareStyles: customSquareStyles, customSquare: customSquare, customBoardStyle: Object.assign(Object.assign({}, nonSelectableBoardStyle), customBoardStyle), position: position, arePiecesDraggable: arePiecesDraggable, autoPromoteToQueen: autoPromoteToQueen, isDraggablePiece: isDraggablePiece, onPromotionCheck: onPromotionCheck, onPieceDrop: onPieceDrop, onSquareClick: clickToMoveEnabled ? handleSquareClick : onSquareClick, onPromotionPieceSelect: clickToMoveEnabled ? handlePromotionPieceSelect : onPromotionPieceSelect, onPieceDragBegin: clickToMoveEnabled ? handlePieceDragBegin : onPieceDragBegin, customArrows: mergedCustomArrows }, promotionControlProps, props)));
1990
2016
  };
1991
2017
 
1992
2018
  const emptyEngineEvaluation = () => ({
@@ -2914,9 +2940,7 @@ const PlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPr
2914
2940
  /** Draggable analysis board (no surrounding layout chrome). */
2915
2941
  const AnalysisChessboardView = ({ model }) => {
2916
2942
  var _a;
2917
- return (jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", customSquareStyles: model.lastMove
2918
- ? getLastMoveSquareStyles(model.lastMove.from, model.lastMove.to, model.theme)
2919
- : {} }) }));
2943
+ return (jsx(ChessboardDnDProvider, { children: jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", lastMoveUci: model.lastMoveUci }) }));
2920
2944
  };
2921
2945
 
2922
2946
  class AnalysisPosition {
@@ -3172,22 +3196,26 @@ class AnalysisPosition {
3172
3196
  return true;
3173
3197
  }
3174
3198
  getLastMoveSquares() {
3175
- const navPly = this.getNavPly();
3176
- if (navPly === 0) {
3199
+ const uci = this.getLastMoveUci();
3200
+ if (!uci) {
3177
3201
  return null;
3178
3202
  }
3179
- let uci;
3180
- if (this.variation && this.variationCursor > 0) {
3181
- uci = this.variation.moves[this.variationCursor - 1];
3182
- }
3183
- else {
3184
- uci = this.solutionMoves[this.mainPly - 1];
3185
- }
3186
3203
  return {
3187
3204
  from: uci.slice(0, 2),
3188
3205
  to: uci.slice(2, 4),
3189
3206
  };
3190
3207
  }
3208
+ getLastMoveUci() {
3209
+ var _a;
3210
+ const navPly = this.getNavPly();
3211
+ if (navPly === 0) {
3212
+ return null;
3213
+ }
3214
+ if (this.variation && this.variationCursor > 0) {
3215
+ return this.variation.moves[this.variationCursor - 1];
3216
+ }
3217
+ return (_a = this.solutionMoves[this.mainPly - 1]) !== null && _a !== void 0 ? _a : null;
3218
+ }
3191
3219
  fen() {
3192
3220
  return this.chess.fen();
3193
3221
  }
@@ -3197,7 +3225,7 @@ class AnalysisPosition {
3197
3225
  }
3198
3226
 
3199
3227
  const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, engine, }) => {
3200
- var _a, _b, _c, _d;
3228
+ var _a;
3201
3229
  const skipBackdropCloseRef = useRef(true);
3202
3230
  const analysisPosition = useMemo(() => new AnalysisPosition(analysisContext), [analysisContext]);
3203
3231
  const [navRevision, setNavRevision] = useState(0);
@@ -3213,12 +3241,7 @@ const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, en
3213
3241
  }, [analysisContext, analysisPosition]);
3214
3242
  const fen = analysisPosition.fen();
3215
3243
  const engineEnabled = (_a = engine === null || engine === void 0 ? void 0 : engine.enabled) !== null && _a !== void 0 ? _a : true;
3216
- const engineEvaluation = useAnalysisEngine(fen, {
3217
- enabled: engineEnabled,
3218
- depth: (_b = engine === null || engine === void 0 ? void 0 : engine.depth) !== null && _b !== void 0 ? _b : 16,
3219
- multiPv: (_c = engine === null || engine === void 0 ? void 0 : engine.multiPv) !== null && _c !== void 0 ? _c : 2,
3220
- scriptUrl: (_d = engine === null || engine === void 0 ? void 0 : engine.scriptUrl) !== null && _d !== void 0 ? _d : DEFAULT_STOCKFISH_SCRIPT_URL,
3221
- });
3244
+ const engineEvaluation = useAnalysisEngine(fen, Object.assign(Object.assign({ depth: 16, multiPv: 2, scriptUrl: DEFAULT_STOCKFISH_SCRIPT_URL }, engine), { enabled: engineEnabled }));
3222
3245
  return {
3223
3246
  theme,
3224
3247
  boardWidth,
@@ -3232,6 +3255,7 @@ const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, en
3232
3255
  engineEvaluation,
3233
3256
  engineEnabled,
3234
3257
  lastMove: analysisPosition.getLastMoveSquares(),
3258
+ lastMoveUci: analysisPosition.getLastMoveUci(),
3235
3259
  checkSquare: analysisPosition.getCheckSquare(),
3236
3260
  onSelectPly: (ply) => {
3237
3261
  analysisPosition.goToNavPly(ply);
@@ -3846,6 +3870,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3846
3870
  return {
3847
3871
  fen: null,
3848
3872
  arrows: [],
3873
+ lastMoveUci: null,
3849
3874
  animating: false,
3850
3875
  };
3851
3876
  }
@@ -3856,6 +3881,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3856
3881
  return {
3857
3882
  fen: fenAfterWrong !== null && fenAfterWrong !== void 0 ? fenAfterWrong : setupFen,
3858
3883
  arrows: [],
3884
+ lastMoveUci: attemptedUci,
3859
3885
  animating: false,
3860
3886
  };
3861
3887
  case 'refutation': {
@@ -3865,6 +3891,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3865
3891
  return {
3866
3892
  fen: (_a = fenAfterRefutation !== null && fenAfterRefutation !== void 0 ? fenAfterRefutation : fenAfterWrong) !== null && _a !== void 0 ? _a : setupFen,
3867
3893
  arrows: [],
3894
+ lastMoveUci: refutationUci,
3868
3895
  animating: Boolean(fenAfterRefutation),
3869
3896
  };
3870
3897
  }
@@ -3872,18 +3899,21 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3872
3899
  return {
3873
3900
  fen: setupFen,
3874
3901
  arrows: [],
3902
+ lastMoveUci: null,
3875
3903
  animating: false,
3876
3904
  };
3877
3905
  case 'answer':
3878
3906
  return {
3879
3907
  fen: setupFen,
3880
3908
  arrows: expectedMoveArrow(expectedUci, answerArrowColor),
3909
+ lastMoveUci: null,
3881
3910
  animating: false,
3882
3911
  };
3883
3912
  default:
3884
3913
  return {
3885
3914
  fen: setupFen,
3886
3915
  arrows: [],
3916
+ lastMoveUci: null,
3887
3917
  animating: false,
3888
3918
  };
3889
3919
  }
@@ -4056,8 +4086,9 @@ function useMissBoard({ feedback, expectedUci, positionFen, answerArrowColor, au
4056
4086
  customArrows,
4057
4087
  boardPosition,
4058
4088
  boardAnimating: missSequence.display.animating,
4089
+ lastMoveUci: missSequence.display.lastMoveUci,
4059
4090
  wrapDropHandler,
4060
4091
  };
4061
4092
  }
4062
4093
 
4063
- export { AnalysisBoard, AnalysisBoardCore, AnalysisBoardCoreView, AnalysisBoardLayout, AnalysisChessboardView, AnalysisEngineProvider, AnalysisErrorBoundary, AnalysisPosition, BOARD_THEMES, BOARD_THEME_IDS, CORRECT_MOVE_FEEDBACK_MS, ChessboardDnDProvider, ChessboardThemeContext, CorrectMoveCheckBadge, DEFAULT_ANALYSIS_LAYOUT, DEFAULT_BOARD_THEME, DEFAULT_STOCKFISH_SCRIPT_URL, DefaultAnalysisContainer, DefaultAnalysisSidebar, DefaultPlyNavigation, EngineEvaluationPanel, HighlightChessboard, MISS_MOVE_ANIMATION_MS, MISS_REFUTATION_MAX_WAIT_MS, MISS_REFUTATION_PAUSE_MS, MISS_WRONG_PAUSE_MS, PlyNavigation, REFUTATION_EVAL_GAP_CP, REFUTATION_EVAL_GAP_PAWNS, StockfishBrowserEngine, ThemeProvider, analysisBoardHighlightColors, analysisSidebarColors, applyUciMove, boardSquareHighlightColors, boardThemeFromLegacyUiTheme, createExpectedMoveDropHandler, createSidebarRowBandCounters, defaultRenderPlyNavigation, emptyEngineEvaluation, evaluateExpectedMoveDrop, fenAfterUci, formatEvaluation, formatPvPreview, getAnalysisModalStyles, getBoardThemeStyles, getCheckSquareFromChess, getLastMoveSquareStyles, getMissDisplay, getSidebarRowBackground, getStylesForTheme, isAnalyzableFen, isBoardThemeId, isEditableKeyboardTarget, lineEvalCpForGap, matchesExpectedUci, navButtonStyle, navRowStyle, normalizeEvalForWhite, normalizePvMoves, normalizeSubscriberOptions, parseUciInfoLine, parseUciMove, plyLabelStyle, palette as plyNavigationPalette, refutationEngineOptions, refutationEvalGapCp, refutationFromEvaluation, resolveStockfishScriptUrl, resolveStockfishWasmUrl, resolveStockfishWorkerUrl, scrubberInputStyle, splitWorkerLines, uciFromDrop, uciPvToSan, useAnalysisBoardModel, useAnalysisEngine, useAnalysisEngineContext, useBoardRevision, useChessboardTheme, useCorrectMoveFeedback, useMissBoard, useMissRefutation, useMissSequence, usePositionKeyboardNav, useTheme };
4094
+ export { AnalysisBoard, AnalysisBoardCore, AnalysisBoardCoreView, AnalysisBoardLayout, AnalysisChessboardView, AnalysisEngineProvider, AnalysisErrorBoundary, AnalysisPosition, BOARD_THEMES, BOARD_THEME_IDS, CORRECT_MOVE_FEEDBACK_MS, ChessboardDnDProvider, ChessboardThemeContext, CorrectMoveCheckBadge, DEFAULT_ANALYSIS_LAYOUT, DEFAULT_BOARD_THEME, DEFAULT_LAST_MOVE_ARROW_COLOR, DEFAULT_STOCKFISH_SCRIPT_URL, DefaultAnalysisContainer, DefaultAnalysisSidebar, DefaultPlyNavigation, EngineEvaluationPanel, HighlightChessboard, MISS_MOVE_ANIMATION_MS, MISS_REFUTATION_MAX_WAIT_MS, MISS_REFUTATION_PAUSE_MS, MISS_WRONG_PAUSE_MS, PlyNavigation, REFUTATION_EVAL_GAP_CP, REFUTATION_EVAL_GAP_PAWNS, StockfishBrowserEngine, ThemeProvider, analysisBoardHighlightColors, analysisSidebarColors, applyUciMove, boardSquareHighlightColors, boardThemeFromLegacyUiTheme, createExpectedMoveDropHandler, createSidebarRowBandCounters, defaultRenderPlyNavigation, emptyEngineEvaluation, evaluateExpectedMoveDrop, fenAfterUci, formatEvaluation, formatPvPreview, getAnalysisModalStyles, getBoardThemeStyles, getCheckSquareFromChess, getLastMoveSquareStyles, getMissDisplay, getSidebarRowBackground, getStylesForTheme, isAnalyzableFen, isBoardThemeId, isEditableKeyboardTarget, lastMoveArrowFromUci, lastMoveUciAtPly, lineEvalCpForGap, matchesExpectedUci, mergeCustomArrowsWithLastMove, navButtonStyle, navRowStyle, normalizeEvalForWhite, normalizePvMoves, normalizeSubscriberOptions, parseUciInfoLine, parseUciMove, plyLabelStyle, palette as plyNavigationPalette, refutationEngineOptions, refutationEvalGapCp, refutationFromEvaluation, resolveStockfishScriptUrl, resolveStockfishWasmUrl, resolveStockfishWorkerUrl, scrubberInputStyle, splitWorkerLines, uciFromDrop, uciPvToSan, uciToArrow, useAnalysisBoardModel, useAnalysisEngine, useAnalysisEngineContext, useBoardRevision, useChessboardTheme, useCorrectMoveFeedback, useMissBoard, useMissRefutation, useMissSequence, usePositionKeyboardNav, useTheme };
package/dist/index.js CHANGED
@@ -1783,6 +1783,31 @@ function createFeedbackSquareRenderer(correctMoveSquare) {
1783
1783
  };
1784
1784
  }
1785
1785
 
1786
+ /** Subtle green arrow visible on light and dark boards (Lichess-style). */
1787
+ const DEFAULT_LAST_MOVE_ARROW_COLOR = 'rgba(155, 199, 0, 0.85)';
1788
+ const uciToArrow = (uci, color = DEFAULT_LAST_MOVE_ARROW_COLOR) => [uci.slice(0, 2), uci.slice(2, 4), color];
1789
+ const lastMoveArrowFromUci = (uci, color = DEFAULT_LAST_MOVE_ARROW_COLOR) => {
1790
+ if (!uci || uci.length < 4) {
1791
+ return [];
1792
+ }
1793
+ return [uciToArrow(uci, color)];
1794
+ };
1795
+ /** UCI of the move that produced the position at {@link plyIndex}. */
1796
+ const lastMoveUciAtPly = (movesUci, plyIndex) => {
1797
+ var _a;
1798
+ if (plyIndex <= 0) {
1799
+ return null;
1800
+ }
1801
+ return (_a = movesUci[plyIndex - 1]) !== null && _a !== void 0 ? _a : null;
1802
+ };
1803
+ const mergeCustomArrowsWithLastMove = (customArrows, lastMoveUci, lastMoveArrowColor) => {
1804
+ const lastMove = lastMoveArrowFromUci(lastMoveUci, lastMoveArrowColor);
1805
+ if (!lastMove.length) {
1806
+ return customArrows !== null && customArrows !== void 0 ? customArrows : [];
1807
+ }
1808
+ return [...lastMove, ...(customArrows !== null && customArrows !== void 0 ? customArrows : [])];
1809
+ };
1810
+
1786
1811
  function defaultPromotionCheck(sourceSquare, targetSquare, piece) {
1787
1812
  return (((piece === 'wP' && sourceSquare[1] === '7' && targetSquare[1] === '8') ||
1788
1813
  (piece === 'bP' && sourceSquare[1] === '2' && targetSquare[1] === '1')) &&
@@ -1955,7 +1980,7 @@ const getFeedbackHighlighting = (hintSquare, incorrectMoveSquare) => {
1955
1980
  return styles;
1956
1981
  };
1957
1982
  const HighlightChessboard = (_a) => {
1958
- var { checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare = null, clickToMove, customSquareStyles: extraSquareStyles, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp } = _a, props = __rest(_a, ["checkSquare", "hintSquare", "incorrectMoveSquare", "correctMoveSquare", "clickToMove", "customSquareStyles", "customBoardStyle", "onPieceDrop", "position", "arePiecesDraggable", "autoPromoteToQueen", "isDraggablePiece", "onPromotionCheck", "onSquareClick", "onPromotionPieceSelect", "onPieceDragBegin", "showPromotionDialog", "promotionToSquare"]);
1983
+ var { checkSquare, hintSquare, incorrectMoveSquare, correctMoveSquare = null, lastMoveUci = null, lastMoveArrowColor, clickToMove, customSquareStyles: extraSquareStyles, customArrows, customBoardStyle, onPieceDrop, position, arePiecesDraggable, autoPromoteToQueen, isDraggablePiece, onPromotionCheck, onSquareClick, onPromotionPieceSelect, onPieceDragBegin, showPromotionDialog: showPromotionDialogProp, promotionToSquare: promotionToSquareProp } = _a, props = __rest(_a, ["checkSquare", "hintSquare", "incorrectMoveSquare", "correctMoveSquare", "lastMoveUci", "lastMoveArrowColor", "clickToMove", "customSquareStyles", "customArrows", "customBoardStyle", "onPieceDrop", "position", "arePiecesDraggable", "autoPromoteToQueen", "isDraggablePiece", "onPromotionCheck", "onSquareClick", "onPromotionPieceSelect", "onPieceDragBegin", "showPromotionDialog", "promotionToSquare"]);
1959
1984
  const { customDarkSquareStyle, customLightSquareStyle } = useChessboardTheme();
1960
1985
  const clickToMoveEnabled = clickToMove !== false && typeof onPieceDrop === 'function';
1961
1986
  const { clickSquareStyles, handleSquareClick, handlePromotionPieceSelect, handlePieceDragBegin, showPromotionDialog: clickPromotionDialog, promotionToSquare: clickPromotionToSquare, } = useClickToMove({
@@ -1976,6 +2001,7 @@ const HighlightChessboard = (_a) => {
1976
2001
  const customSquare = react.useMemo(() => correctMoveSquare
1977
2002
  ? createFeedbackSquareRenderer(correctMoveSquare)
1978
2003
  : undefined, [correctMoveSquare]);
2004
+ const mergedCustomArrows = react.useMemo(() => mergeCustomArrowsWithLastMove(customArrows, lastMoveUci, lastMoveArrowColor), [customArrows, lastMoveArrowColor, lastMoveUci]);
1979
2005
  const promotionControlProps = clickPromotionDialog
1980
2006
  ? {
1981
2007
  showPromotionDialog: true,
@@ -1988,7 +2014,7 @@ const HighlightChessboard = (_a) => {
1988
2014
  promotionToSquare: promotionToSquareProp,
1989
2015
  }
1990
2016
  : {};
1991
- return (jsxRuntime.jsx(reactChessboard.Chessboard, Object.assign({ customDarkSquareStyle: customDarkSquareStyle, customLightSquareStyle: customLightSquareStyle, customSquareStyles: customSquareStyles, customSquare: customSquare, customBoardStyle: Object.assign(Object.assign({}, nonSelectableBoardStyle), customBoardStyle), position: position, arePiecesDraggable: arePiecesDraggable, autoPromoteToQueen: autoPromoteToQueen, isDraggablePiece: isDraggablePiece, onPromotionCheck: onPromotionCheck, onPieceDrop: onPieceDrop, onSquareClick: clickToMoveEnabled ? handleSquareClick : onSquareClick, onPromotionPieceSelect: clickToMoveEnabled ? handlePromotionPieceSelect : onPromotionPieceSelect, onPieceDragBegin: clickToMoveEnabled ? handlePieceDragBegin : onPieceDragBegin }, promotionControlProps, props)));
2017
+ return (jsxRuntime.jsx(reactChessboard.Chessboard, Object.assign({ customDarkSquareStyle: customDarkSquareStyle, customLightSquareStyle: customLightSquareStyle, customSquareStyles: customSquareStyles, customSquare: customSquare, customBoardStyle: Object.assign(Object.assign({}, nonSelectableBoardStyle), customBoardStyle), position: position, arePiecesDraggable: arePiecesDraggable, autoPromoteToQueen: autoPromoteToQueen, isDraggablePiece: isDraggablePiece, onPromotionCheck: onPromotionCheck, onPieceDrop: onPieceDrop, onSquareClick: clickToMoveEnabled ? handleSquareClick : onSquareClick, onPromotionPieceSelect: clickToMoveEnabled ? handlePromotionPieceSelect : onPromotionPieceSelect, onPieceDragBegin: clickToMoveEnabled ? handlePieceDragBegin : onPieceDragBegin, customArrows: mergedCustomArrows }, promotionControlProps, props)));
1992
2018
  };
1993
2019
 
1994
2020
  const emptyEngineEvaluation = () => ({
@@ -2916,9 +2942,7 @@ const PlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPr
2916
2942
  /** Draggable analysis board (no surrounding layout chrome). */
2917
2943
  const AnalysisChessboardView = ({ model }) => {
2918
2944
  var _a;
2919
- return (jsxRuntime.jsx(ChessboardDnDProvider, { children: jsxRuntime.jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", customSquareStyles: model.lastMove
2920
- ? getLastMoveSquareStyles(model.lastMove.from, model.lastMove.to, model.theme)
2921
- : {} }) }));
2945
+ return (jsxRuntime.jsx(ChessboardDnDProvider, { children: jsxRuntime.jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", lastMoveUci: model.lastMoveUci }) }));
2922
2946
  };
2923
2947
 
2924
2948
  class AnalysisPosition {
@@ -3174,22 +3198,26 @@ class AnalysisPosition {
3174
3198
  return true;
3175
3199
  }
3176
3200
  getLastMoveSquares() {
3177
- const navPly = this.getNavPly();
3178
- if (navPly === 0) {
3201
+ const uci = this.getLastMoveUci();
3202
+ if (!uci) {
3179
3203
  return null;
3180
3204
  }
3181
- let uci;
3182
- if (this.variation && this.variationCursor > 0) {
3183
- uci = this.variation.moves[this.variationCursor - 1];
3184
- }
3185
- else {
3186
- uci = this.solutionMoves[this.mainPly - 1];
3187
- }
3188
3205
  return {
3189
3206
  from: uci.slice(0, 2),
3190
3207
  to: uci.slice(2, 4),
3191
3208
  };
3192
3209
  }
3210
+ getLastMoveUci() {
3211
+ var _a;
3212
+ const navPly = this.getNavPly();
3213
+ if (navPly === 0) {
3214
+ return null;
3215
+ }
3216
+ if (this.variation && this.variationCursor > 0) {
3217
+ return this.variation.moves[this.variationCursor - 1];
3218
+ }
3219
+ return (_a = this.solutionMoves[this.mainPly - 1]) !== null && _a !== void 0 ? _a : null;
3220
+ }
3193
3221
  fen() {
3194
3222
  return this.chess.fen();
3195
3223
  }
@@ -3199,7 +3227,7 @@ class AnalysisPosition {
3199
3227
  }
3200
3228
 
3201
3229
  const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, engine, }) => {
3202
- var _a, _b, _c, _d;
3230
+ var _a;
3203
3231
  const skipBackdropCloseRef = react.useRef(true);
3204
3232
  const analysisPosition = react.useMemo(() => new AnalysisPosition(analysisContext), [analysisContext]);
3205
3233
  const [navRevision, setNavRevision] = react.useState(0);
@@ -3215,12 +3243,7 @@ const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, en
3215
3243
  }, [analysisContext, analysisPosition]);
3216
3244
  const fen = analysisPosition.fen();
3217
3245
  const engineEnabled = (_a = engine === null || engine === void 0 ? void 0 : engine.enabled) !== null && _a !== void 0 ? _a : true;
3218
- const engineEvaluation = useAnalysisEngine(fen, {
3219
- enabled: engineEnabled,
3220
- depth: (_b = engine === null || engine === void 0 ? void 0 : engine.depth) !== null && _b !== void 0 ? _b : 16,
3221
- multiPv: (_c = engine === null || engine === void 0 ? void 0 : engine.multiPv) !== null && _c !== void 0 ? _c : 2,
3222
- scriptUrl: (_d = engine === null || engine === void 0 ? void 0 : engine.scriptUrl) !== null && _d !== void 0 ? _d : DEFAULT_STOCKFISH_SCRIPT_URL,
3223
- });
3246
+ const engineEvaluation = useAnalysisEngine(fen, Object.assign(Object.assign({ depth: 16, multiPv: 2, scriptUrl: DEFAULT_STOCKFISH_SCRIPT_URL }, engine), { enabled: engineEnabled }));
3224
3247
  return {
3225
3248
  theme,
3226
3249
  boardWidth,
@@ -3234,6 +3257,7 @@ const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, en
3234
3257
  engineEvaluation,
3235
3258
  engineEnabled,
3236
3259
  lastMove: analysisPosition.getLastMoveSquares(),
3260
+ lastMoveUci: analysisPosition.getLastMoveUci(),
3237
3261
  checkSquare: analysisPosition.getCheckSquare(),
3238
3262
  onSelectPly: (ply) => {
3239
3263
  analysisPosition.goToNavPly(ply);
@@ -3848,6 +3872,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3848
3872
  return {
3849
3873
  fen: null,
3850
3874
  arrows: [],
3875
+ lastMoveUci: null,
3851
3876
  animating: false,
3852
3877
  };
3853
3878
  }
@@ -3858,6 +3883,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3858
3883
  return {
3859
3884
  fen: fenAfterWrong !== null && fenAfterWrong !== void 0 ? fenAfterWrong : setupFen,
3860
3885
  arrows: [],
3886
+ lastMoveUci: attemptedUci,
3861
3887
  animating: false,
3862
3888
  };
3863
3889
  case 'refutation': {
@@ -3867,6 +3893,7 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3867
3893
  return {
3868
3894
  fen: (_a = fenAfterRefutation !== null && fenAfterRefutation !== void 0 ? fenAfterRefutation : fenAfterWrong) !== null && _a !== void 0 ? _a : setupFen,
3869
3895
  arrows: [],
3896
+ lastMoveUci: refutationUci,
3870
3897
  animating: Boolean(fenAfterRefutation),
3871
3898
  };
3872
3899
  }
@@ -3874,18 +3901,21 @@ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor)
3874
3901
  return {
3875
3902
  fen: setupFen,
3876
3903
  arrows: [],
3904
+ lastMoveUci: null,
3877
3905
  animating: false,
3878
3906
  };
3879
3907
  case 'answer':
3880
3908
  return {
3881
3909
  fen: setupFen,
3882
3910
  arrows: expectedMoveArrow(expectedUci, answerArrowColor),
3911
+ lastMoveUci: null,
3883
3912
  animating: false,
3884
3913
  };
3885
3914
  default:
3886
3915
  return {
3887
3916
  fen: setupFen,
3888
3917
  arrows: [],
3918
+ lastMoveUci: null,
3889
3919
  animating: false,
3890
3920
  };
3891
3921
  }
@@ -4058,6 +4088,7 @@ function useMissBoard({ feedback, expectedUci, positionFen, answerArrowColor, au
4058
4088
  customArrows,
4059
4089
  boardPosition,
4060
4090
  boardAnimating: missSequence.display.animating,
4091
+ lastMoveUci: missSequence.display.lastMoveUci,
4061
4092
  wrapDropHandler,
4062
4093
  };
4063
4094
  }
@@ -4078,6 +4109,7 @@ exports.ChessboardThemeContext = ChessboardThemeContext;
4078
4109
  exports.CorrectMoveCheckBadge = CorrectMoveCheckBadge;
4079
4110
  exports.DEFAULT_ANALYSIS_LAYOUT = DEFAULT_ANALYSIS_LAYOUT;
4080
4111
  exports.DEFAULT_BOARD_THEME = DEFAULT_BOARD_THEME;
4112
+ exports.DEFAULT_LAST_MOVE_ARROW_COLOR = DEFAULT_LAST_MOVE_ARROW_COLOR;
4081
4113
  exports.DEFAULT_STOCKFISH_SCRIPT_URL = DEFAULT_STOCKFISH_SCRIPT_URL;
4082
4114
  exports.DefaultAnalysisContainer = DefaultAnalysisContainer;
4083
4115
  exports.DefaultAnalysisSidebar = DefaultAnalysisSidebar;
@@ -4116,8 +4148,11 @@ exports.getStylesForTheme = getStylesForTheme;
4116
4148
  exports.isAnalyzableFen = isAnalyzableFen;
4117
4149
  exports.isBoardThemeId = isBoardThemeId;
4118
4150
  exports.isEditableKeyboardTarget = isEditableKeyboardTarget;
4151
+ exports.lastMoveArrowFromUci = lastMoveArrowFromUci;
4152
+ exports.lastMoveUciAtPly = lastMoveUciAtPly;
4119
4153
  exports.lineEvalCpForGap = lineEvalCpForGap;
4120
4154
  exports.matchesExpectedUci = matchesExpectedUci;
4155
+ exports.mergeCustomArrowsWithLastMove = mergeCustomArrowsWithLastMove;
4121
4156
  exports.navButtonStyle = navButtonStyle;
4122
4157
  exports.navRowStyle = navRowStyle;
4123
4158
  exports.normalizeEvalForWhite = normalizeEvalForWhite;
@@ -4137,6 +4172,7 @@ exports.scrubberInputStyle = scrubberInputStyle;
4137
4172
  exports.splitWorkerLines = splitWorkerLines;
4138
4173
  exports.uciFromDrop = uciFromDrop;
4139
4174
  exports.uciPvToSan = uciPvToSan;
4175
+ exports.uciToArrow = uciToArrow;
4140
4176
  exports.useAnalysisBoardModel = useAnalysisBoardModel;
4141
4177
  exports.useAnalysisEngine = useAnalysisEngine;
4142
4178
  exports.useAnalysisEngineContext = useAnalysisEngineContext;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-chess-core",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Shared React chessboard theme, highlights, browser Stockfish, and analysis board",
5
5
  "license": "MIT",
6
6
  "author": "Robert Blackwell",