kokopu-react 3.0.2 → 3.2.0

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.
Files changed (253) hide show
  1. package/.eslintrc.yml +1 -1
  2. package/CHANGELOG.md +13 -1
  3. package/dist/lib/chessboard/BoardProperties.d.ts +91 -0
  4. package/dist/lib/chessboard/BoardProperties.js +43 -0
  5. package/dist/lib/chessboard/BoardProperties.js.map +1 -0
  6. package/dist/lib/chessboard/Chessboard.css +13 -13
  7. package/dist/lib/chessboard/Chessboard.d.ts +14 -58
  8. package/dist/lib/chessboard/Chessboard.js +26 -92
  9. package/dist/lib/chessboard/Chessboard.js.map +1 -1
  10. package/dist/lib/chessboard/ChessboardImpl.js.map +1 -1
  11. package/dist/lib/chessboard/Draggable.js.map +1 -1
  12. package/dist/lib/chessboard/Motion.js.map +1 -1
  13. package/dist/lib/chessboard/colorsets.js.map +1 -1
  14. package/dist/lib/chessboard/piecesets.js.map +1 -1
  15. package/dist/lib/errorbox/ErrorBox.css +18 -18
  16. package/dist/lib/errorbox/ErrorBox.js.map +1 -1
  17. package/dist/lib/errorbox/parsing.d.ts +57 -0
  18. package/dist/lib/errorbox/parsing.js +140 -0
  19. package/dist/lib/errorbox/parsing.js.map +1 -0
  20. package/dist/lib/exception.js.map +1 -1
  21. package/dist/lib/i18n.d.ts +6 -0
  22. package/dist/lib/i18n.js +7 -0
  23. package/dist/lib/i18n.js.map +1 -1
  24. package/dist/lib/icons/AnnotationSymbolShape.css +6 -6
  25. package/dist/lib/icons/AnnotationSymbolShape.js.map +1 -1
  26. package/dist/lib/icons/ArrowMarkerIcon.js.map +1 -1
  27. package/dist/lib/icons/ArrowTip.js.map +1 -1
  28. package/dist/lib/icons/ChessPieceIcon.js.map +1 -1
  29. package/dist/lib/icons/SquareMarkerIcon.js.map +1 -1
  30. package/dist/lib/icons/TextMarkerIcon.js.map +1 -1
  31. package/dist/lib/icons/arrow.css +1 -1
  32. package/dist/lib/index.d.ts +5 -1
  33. package/dist/lib/index.js +3 -0
  34. package/dist/lib/index.js.map +1 -1
  35. package/dist/lib/movetext/Movetext.css +27 -40
  36. package/dist/lib/movetext/Movetext.d.ts +9 -43
  37. package/dist/lib/movetext/Movetext.js +15 -67
  38. package/dist/lib/movetext/Movetext.js.map +1 -1
  39. package/dist/lib/movetext/MovetextImpl.d.ts +7 -28
  40. package/dist/lib/movetext/MovetextImpl.js +15 -94
  41. package/dist/lib/movetext/MovetextImpl.js.map +1 -1
  42. package/dist/lib/movetext/fonts.css +7 -7
  43. package/dist/lib/movetext/htmlFilter.js.map +1 -1
  44. package/dist/lib/movetext/moveFormatter.js.map +1 -1
  45. package/dist/lib/navigationboard/NavigationBoard.d.ts +156 -0
  46. package/dist/lib/navigationboard/NavigationBoard.js +195 -0
  47. package/dist/lib/navigationboard/NavigationBoard.js.map +1 -0
  48. package/dist/lib/navigationboard/NavigationButton.d.ts +54 -0
  49. package/dist/lib/navigationboard/NavigationButton.js +35 -0
  50. package/dist/lib/navigationboard/NavigationButton.js.map +1 -0
  51. package/dist/lib/navigationboard/NavigationField.css +36 -0
  52. package/dist/lib/navigationboard/NavigationField.d.ts +81 -0
  53. package/dist/lib/navigationboard/NavigationField.js +154 -0
  54. package/dist/lib/navigationboard/NavigationField.js.map +1 -0
  55. package/dist/lib/navigationboard/NavigationToolbar.css +54 -0
  56. package/dist/lib/navigationboard/NavigationToolbar.d.ts +34 -0
  57. package/dist/lib/navigationboard/NavigationToolbar.js +73 -0
  58. package/dist/lib/navigationboard/NavigationToolbar.js.map +1 -0
  59. package/dist/lib/navigationboard/iconPaths.d.ts +29 -0
  60. package/dist/lib/navigationboard/iconPaths.js +91 -0
  61. package/dist/lib/navigationboard/iconPaths.js.map +1 -0
  62. package/dist/lib/sanitization.js.map +1 -1
  63. package/dist/lib/types.js.map +1 -1
  64. package/dist/lib/util.js.map +1 -1
  65. package/doc_src/demo/PageChessboardBase.tsx +115 -115
  66. package/doc_src/demo/PageChessboardInteraction.tsx +303 -303
  67. package/doc_src/demo/PageChessboardMove.tsx +173 -156
  68. package/doc_src/demo/PageChessboardSmallScreens.tsx +94 -94
  69. package/doc_src/demo/PageMovetextBase.tsx +180 -180
  70. package/doc_src/demo/PageMovetextInteraction.tsx +123 -123
  71. package/doc_src/demo/PageNavigationBoardBase.tsx +201 -0
  72. package/doc_src/demo/PageNavigationBoardUncontrolledVsControlled.tsx +114 -0
  73. package/doc_src/demo/decs.d.ts +4 -4
  74. package/doc_src/demo/demo.css +12 -12
  75. package/doc_src/demo/game-3.pgn +10 -0
  76. package/doc_src/demo/util.ts +1 -1
  77. package/doc_src/examples/Movetext.md +1 -1
  78. package/doc_src/examples/NavigationBoard.md +15 -0
  79. package/doc_src/theming/LogoRenderer.js +13 -13
  80. package/doc_src/theming/theming.css +5 -5
  81. package/package.json +95 -95
  82. package/scripts/doc.styleguide.config.js +133 -123
  83. package/scripts/doc.tsconfig.json +5 -5
  84. package/scripts/docker-compose.yml +1 -1
  85. package/scripts/test-graphic.tsconfig.json +5 -5
  86. package/scripts/test-graphic.webpack.config.js +57 -57
  87. package/scripts/test-headless.webpack.config.js +29 -29
  88. package/src/chessboard/BoardProperties.ts +131 -0
  89. package/src/chessboard/Chessboard.css +13 -13
  90. package/src/chessboard/Chessboard.tsx +389 -518
  91. package/src/chessboard/ChessboardImpl.tsx +557 -557
  92. package/src/chessboard/Draggable.tsx +185 -185
  93. package/src/chessboard/Motion.tsx +67 -67
  94. package/src/chessboard/colorsets.ts +66 -66
  95. package/src/chessboard/piecesets.ts +84 -84
  96. package/src/decs.d.ts +2 -2
  97. package/src/errorbox/ErrorBox.css +18 -18
  98. package/src/errorbox/ErrorBox.tsx +75 -75
  99. package/src/errorbox/parsing.tsx +158 -0
  100. package/src/exception.ts +17 -17
  101. package/src/i18n.ts +8 -0
  102. package/src/icons/AnnotationSymbolShape.css +6 -6
  103. package/src/icons/AnnotationSymbolShape.tsx +31 -31
  104. package/src/icons/ArrowMarkerIcon.tsx +34 -34
  105. package/src/icons/ArrowTip.tsx +14 -14
  106. package/src/icons/ChessPieceIcon.tsx +45 -45
  107. package/src/icons/SquareMarkerIcon.tsx +20 -20
  108. package/src/icons/TextMarkerIcon.tsx +29 -29
  109. package/src/icons/arrow.css +1 -1
  110. package/src/index.ts +8 -3
  111. package/src/movetext/Movetext.css +27 -40
  112. package/src/movetext/Movetext.tsx +163 -228
  113. package/src/movetext/MovetextImpl.tsx +351 -438
  114. package/src/movetext/fonts.css +7 -7
  115. package/src/movetext/htmlFilter.tsx +129 -129
  116. package/src/movetext/moveFormatter.tsx +37 -37
  117. package/src/navigationboard/NavigationBoard.tsx +350 -0
  118. package/src/navigationboard/NavigationButton.ts +72 -0
  119. package/src/navigationboard/NavigationField.css +36 -0
  120. package/src/navigationboard/NavigationField.tsx +185 -0
  121. package/src/navigationboard/NavigationToolbar.css +54 -0
  122. package/src/navigationboard/NavigationToolbar.tsx +117 -0
  123. package/src/navigationboard/iconPaths.ts +98 -0
  124. package/src/sanitization.ts +17 -17
  125. package/src/types.ts +116 -116
  126. package/src/util.ts +11 -11
  127. package/test/01_types.js +167 -167
  128. package/test/02_chessboard_util.js +114 -114
  129. package/test/03_error_box_graphic.js +16 -16
  130. package/test/04_icons_graphic.js +32 -32
  131. package/test/05_chessboard_graphic.js +60 -58
  132. package/test/06_chessboard_animation.js +36 -36
  133. package/test/07_chessboard_interaction.js +101 -101
  134. package/test/08_chessboard_play_moves.js +62 -62
  135. package/test/09_chessboard_small_screens.js +42 -21
  136. package/test/10_movetext_graphic.js +36 -31
  137. package/test/11_movetext_interaction.js +100 -96
  138. package/test/12_navigation_board_base.js +151 -0
  139. package/test/13_navigation_board_graphic.js +68 -0
  140. package/test/14_navigation_board_autoplay.js +100 -0
  141. package/test/common/graphic.js +106 -97
  142. package/test/graphic_references/05_chessboard_graphic/move/illegal_null-move.png +0 -0
  143. package/test/graphic_references/05_chessboard_graphic/move/null-move.png +0 -0
  144. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_bottom_component.png +0 -0
  145. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_top_component.png +0 -0
  146. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_bottom_component.png +0 -0
  147. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_top_component.png +0 -0
  148. package/test/graphic_references/10_movetext_graphic/format_move/format_text.png +0 -0
  149. package/test/graphic_references/10_movetext_graphic/options/wrong_piece_symbol_value.png +0 -0
  150. package/test/graphic_references/11_movetext_interaction/select_moves/click_on_unselected_without_callback.png +0 -0
  151. package/test/graphic_references/12_navigation_board_base/controlled/default.png +0 -0
  152. package/test/graphic_references/12_navigation_board_base/controlled/forbidden_navigation_0.png +0 -0
  153. package/test/graphic_references/12_navigation_board_base/controlled/forbidden_navigation_1.png +0 -0
  154. package/test/graphic_references/12_navigation_board_base/controlled/invalid_node_id.png +0 -0
  155. package/test/graphic_references/12_navigation_board_base/controlled/no_callback_defined_0.png +0 -0
  156. package/test/graphic_references/12_navigation_board_base/controlled/no_callback_defined_1.png +0 -0
  157. package/test/graphic_references/12_navigation_board_base/controlled/overriding_initial_node_id.png +0 -0
  158. package/test/graphic_references/12_navigation_board_base/controlled/regular_navigation_0.png +0 -0
  159. package/test/graphic_references/12_navigation_board_base/controlled/regular_navigation_1.png +0 -0
  160. package/test/graphic_references/12_navigation_board_base/controlled/regular_navigation_2.png +0 -0
  161. package/test/graphic_references/12_navigation_board_base/controlled/regular_navigation_3.png +0 -0
  162. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_0.png +0 -0
  163. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_1.png +0 -0
  164. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_2.png +0 -0
  165. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_0.png +0 -0
  166. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_1.png +0 -0
  167. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_2.png +0 -0
  168. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_at_end.png +0 -0
  169. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_at_specific_id.png +0 -0
  170. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_default.png +0 -0
  171. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_default_with_game_index.png +0 -0
  172. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_on_null-move.png +0 -0
  173. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_0.png +0 -0
  174. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_1.png +0 -0
  175. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_2.png +0 -0
  176. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_3.png +0 -0
  177. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_4.png +0 -0
  178. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_flipping_0.png +0 -0
  179. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_flipping_1.png +0 -0
  180. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_0.png +0 -0
  181. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_1.png +0 -0
  182. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_2.png +0 -0
  183. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_3.png +0 -0
  184. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_4.png +0 -0
  185. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_5.png +0 -0
  186. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_1.png +0 -0
  187. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_2.png +0 -0
  188. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/multiple_buttons.png +0 -0
  189. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/single_button.png +0 -0
  190. package/test/graphic_references/13_navigation_board_graphic/error/pgn_parsing_error.png +0 -0
  191. package/test/graphic_references/13_navigation_board_graphic/error/wrong_game_type.png +0 -0
  192. package/test/graphic_references/13_navigation_board_graphic/theme/custom_1.png +0 -0
  193. package/test/graphic_references/13_navigation_board_graphic/theme/custom_2.png +0 -0
  194. package/test/graphic_references/13_navigation_board_graphic/theme/large.png +0 -0
  195. package/test/graphic_references/13_navigation_board_graphic/theme/small.png +0 -0
  196. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_acknowledging_is-playing_changes.png +0 -0
  197. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_not_acknowledging_is-playing_changes.png +0 -0
  198. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_acknowledging_node_changes.png +0 -0
  199. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_not_acknowledging_node_changes.png +0 -0
  200. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_reaching_the_end.png +0 -0
  201. package/test/graphic_references/14_navigation_board_autoplay/controlled/already_at_the_end.png +0 -0
  202. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_is-playing_changes.png +0 -0
  203. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_node_changes.png +0 -0
  204. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_at_the_end.png +0 -0
  205. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_close_to_end.png +0 -0
  206. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_is-playing_changes.png +0 -0
  207. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_node_changes.png +0 -0
  208. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_after_stop.png +0 -0
  209. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_before_stop.png +0 -0
  210. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_after_stop.png +0 -0
  211. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_before_stop.png +0 -0
  212. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/initial_state_close_to_end.png +0 -0
  213. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/initial_state_default.png +0 -0
  214. package/test/graphic_test_app/03_error_box_graphic/base.tsx +14 -14
  215. package/test/graphic_test_app/03_error_box_graphic/localization.tsx +1 -1
  216. package/test/graphic_test_app/04_icons_graphic/chess_piece.tsx +7 -7
  217. package/test/graphic_test_app/04_icons_graphic/marker.tsx +15 -15
  218. package/test/graphic_test_app/05_chessboard_graphic/annotations.tsx +7 -7
  219. package/test/graphic_test_app/05_chessboard_graphic/base.tsx +6 -6
  220. package/test/graphic_test_app/05_chessboard_graphic/chess_variants.tsx +7 -7
  221. package/test/graphic_test_app/05_chessboard_graphic/flipped.tsx +5 -5
  222. package/test/graphic_test_app/05_chessboard_graphic/move.tsx +12 -10
  223. package/test/graphic_test_app/05_chessboard_graphic/theme.tsx +12 -12
  224. package/test/graphic_test_app/06_chessboard_animation/base_1.tsx +6 -6
  225. package/test/graphic_test_app/06_chessboard_animation/base_2.tsx +6 -6
  226. package/test/graphic_test_app/06_chessboard_animation/base_3.tsx +6 -6
  227. package/test/graphic_test_app/06_chessboard_animation/on_redraw.tsx +16 -16
  228. package/test/graphic_test_app/07_chessboard_interaction/click_squares.tsx +5 -5
  229. package/test/graphic_test_app/07_chessboard_interaction/edit_arrows.tsx +4 -4
  230. package/test/graphic_test_app/07_chessboard_interaction/error.tsx +2 -2
  231. package/test/graphic_test_app/07_chessboard_interaction/move_pieces.tsx +5 -5
  232. package/test/graphic_test_app/08_chessboard_play_moves/base.tsx +8 -8
  233. package/test/graphic_test_app/08_chessboard_play_moves/promotions.tsx +4 -4
  234. package/test/graphic_test_app/09_chessboard_small_screens/base.tsx +9 -9
  235. package/test/graphic_test_app/09_chessboard_small_screens/top_bottom_components.tsx +48 -0
  236. package/test/graphic_test_app/10_movetext_graphic/base.tsx +4 -4
  237. package/test/graphic_test_app/10_movetext_graphic/error.tsx +6 -6
  238. package/test/graphic_test_app/10_movetext_graphic/format_move.tsx +31 -0
  239. package/test/graphic_test_app/10_movetext_graphic/html.tsx +5 -5
  240. package/test/graphic_test_app/10_movetext_graphic/options.tsx +10 -9
  241. package/test/graphic_test_app/11_movetext_interaction/error.tsx +1 -1
  242. package/test/graphic_test_app/11_movetext_interaction/select_moves.tsx +11 -10
  243. package/test/graphic_test_app/12_navigation_board_base/controlled.tsx +43 -0
  244. package/test/graphic_test_app/12_navigation_board_base/uncontrolled.tsx +56 -0
  245. package/test/graphic_test_app/13_navigation_board_graphic/additional_buttons.tsx +49 -0
  246. package/test/graphic_test_app/13_navigation_board_graphic/error.tsx +34 -0
  247. package/test/graphic_test_app/13_navigation_board_graphic/theme.tsx +38 -0
  248. package/test/graphic_test_app/14_navigation_board_autoplay/controlled.tsx +73 -0
  249. package/test/graphic_test_app/14_navigation_board_autoplay/uncontrolled.tsx +34 -0
  250. package/test/graphic_test_app/common/decs.d.ts +2 -2
  251. package/test/graphic_test_app/common/test_app.css +15 -15
  252. package/test/graphic_test_app/common/test_app.tsx +48 -48
  253. package/tsconfig.json +13 -13
@@ -52,44 +52,44 @@ const FILE_LABELS = 'abcdefgh';
52
52
 
53
53
  interface ChessboardImplProps {
54
54
 
55
- position: Position;
56
- move?: MoveDescriptor;
57
-
58
- squareMarkers?: SquareMarkerSet;
59
- textMarkers?: TextMarkerSet;
60
- arrowMarkers?: ArrowMarkerSet;
61
-
62
- flipped: boolean;
63
- squareSize: number;
64
- coordinateVisible: boolean;
65
- turnVisible: boolean;
66
- moveArrowVisible: boolean;
67
- moveArrowColor: AnnotationColor;
68
- animated: boolean;
69
- colorset: Colorset;
70
- pieceset: Pieceset;
71
-
72
- interactionMode?: 'movePieces' | 'clickSquares' | 'editArrows' | 'playMoves',
73
- editedArrowColor?: AnnotationColor; // mandatory if `interactionMode === 'editArrows'`
74
-
75
- onPieceMoved?: (from: Square, to: Square) => void;
76
- onSquareClicked?: (square: Square) => void;
77
- onArrowEdited?: (from: Square, to: Square) => void;
78
- onMovePlayed?: (move: string) => void;
55
+ position: Position;
56
+ move?: MoveDescriptor;
57
+
58
+ squareMarkers?: SquareMarkerSet;
59
+ textMarkers?: TextMarkerSet;
60
+ arrowMarkers?: ArrowMarkerSet;
61
+
62
+ flipped: boolean;
63
+ squareSize: number;
64
+ coordinateVisible: boolean;
65
+ turnVisible: boolean;
66
+ moveArrowVisible: boolean;
67
+ moveArrowColor: AnnotationColor;
68
+ animated: boolean;
69
+ colorset: Colorset;
70
+ pieceset: Pieceset;
71
+
72
+ interactionMode?: 'movePieces' | 'clickSquares' | 'editArrows' | 'playMoves',
73
+ editedArrowColor?: AnnotationColor; // mandatory if `interactionMode === 'editArrows'`
74
+
75
+ onPieceMoved?: (from: Square, to: Square) => void;
76
+ onSquareClicked?: (square: Square) => void;
77
+ onArrowEdited?: (from: Square, to: Square) => void;
78
+ onMovePlayed?: (move: string) => void;
79
79
  }
80
80
 
81
81
 
82
82
  interface ChessboardImplState {
83
- inhibitedSquare?: Square;
84
- draggedSquare?: Square;
85
- hoveredSquare?: Square;
86
- dragPosition?: { x: number, y: number }; // mandatory if `dragged` is defined
87
- cursorOffset?: { x: number, y: number }; // mandatory if `dragged` is defined
88
- promotionDrawer?: {
89
- origin: Square,
90
- buttons: Piece[],
91
- builder: (promotion: Piece) => string,
92
- };
83
+ inhibitedSquare?: Square;
84
+ draggedSquare?: Square;
85
+ hoveredSquare?: Square;
86
+ dragPosition?: { x: number, y: number }; // mandatory if `dragged` is defined
87
+ cursorOffset?: { x: number, y: number }; // mandatory if `dragged` is defined
88
+ promotionDrawer?: {
89
+ origin: Square,
90
+ buttons: Piece[],
91
+ builder: (promotion: Piece) => string,
92
+ };
93
93
  }
94
94
 
95
95
 
@@ -101,517 +101,517 @@ interface ChessboardImplState {
101
101
  */
102
102
  export class ChessboardImpl extends React.Component<ChessboardImplProps, ChessboardImplState> {
103
103
 
104
- private arrowTipIdSuffix: string = generateRandomId();
105
-
106
- constructor(props: ChessboardImplProps) {
107
- super(props);
108
- this.state = {};
109
- }
110
-
111
- render() {
112
-
113
- let fontSize = 0;
114
-
115
- // Render the squares.
116
- const squares: React.ReactNode[] = [];
117
- forEachSquare(sq => squares.push(this.renderSquare(sq)));
118
-
119
- // Render coordinates.
120
- const rankCoordinates: React.ReactNode[] = [];
121
- const fileCoordinates: React.ReactNode[] = [];
122
- if (this.props.coordinateVisible) {
123
- fontSize = computeCoordinateFontSize(this.props.squareSize);
124
- for (let i = 0; i < 8; ++i) {
125
- rankCoordinates.push(this.renderRankCoordinate(fontSize, i));
126
- fileCoordinates.push(this.renderFileCoordinate(fontSize, i));
127
- }
128
- }
129
-
130
- // Render the whole canvas.
131
- const xmin = Math.round(-RANK_COORDINATE_WIDTH_FACTOR * fontSize);
132
- const ymin = 0;
133
- const xmax = this.props.turnVisible ? 9 * this.props.squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * this.props.squareSize) : 8 * this.props.squareSize;
134
- const ymax = 8 * this.props.squareSize + Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
135
- const viewBox = `${xmin} ${ymin} ${xmax - xmin} ${ymax - ymin}`;
136
- return (
137
- <svg className="kokopu-chessboard" viewBox={viewBox} width={xmax - xmin} height={ymax - ymin}>
138
- <defs>
139
- {this.renderArrowTip('b')}
140
- {this.renderArrowTip('g')}
141
- {this.renderArrowTip('r')}
142
- {this.renderArrowTip('y')}
143
- </defs>
144
- {squares}
145
- {rankCoordinates}
146
- {fileCoordinates}
147
- {this.renderBoardContent()}
148
- </svg>
149
- );
150
- }
151
-
152
- private renderBoardContent() {
153
- if (this.props.move && this.props.animated) {
154
- return (
155
- <Motion duration={MOTION_DURATION}>
156
- {motionCursor => (motionCursor === 1 ? this.renderBoardContentStill() : this.renderBoardContentAnimated(motionCursor))}
157
- </Motion>
158
- );
159
- }
160
- else {
161
- return this.renderBoardContentStill();
162
- }
163
- }
164
-
165
- /**
166
- * Render the board content during the animation.
167
- */
168
- private renderBoardContentAnimated(motionCursor: number) {
169
- const pieces: React.ReactNode[] = [];
170
- forEachSquare(sq => pieces.push(this.renderPieceAnimated(motionCursor, sq)));
171
- return (
172
- <>
173
- {pieces}
174
- {this.renderMoveArrow(motionCursor)}
175
- {this.renderTurnFlag(oppositeColor(this.props.position.turn()))}
176
- </>
177
- );
178
- }
179
-
180
- /**
181
- * Render the board content when the animation has been completed (or if there is no animation).
182
- */
183
- private renderBoardContentStill() {
184
-
185
- const positionStill = this.getPositionStill();
186
-
187
- // Render the square-related objects.
188
- const pieces: React.ReactNode[] = [];
189
- const handles: React.ReactNode[] = [];
190
- forEachSquare(sq => {
191
- pieces.push(this.renderPiece(positionStill, sq));
192
- if (this.props.interactionMode) {
193
- handles.push(this.renderSquareHandle(positionStill, sq));
194
- }
195
- });
196
- return (
197
- <>
198
- {this.renderSquareMarkers()}
199
- {this.renderHoveredSquare()}
200
- {pieces}
201
- {this.renderTextMarkers()}
202
- {this.renderArrowMarkers()}
203
- {this.renderMoveArrow(1)}
204
- {handles}
205
- {this.renderPromotionDrawer(positionStill)}
206
- {this.renderDraggedPiece(positionStill)}
207
- {this.renderDraggedArrow()}
208
- {this.renderTurnFlag(positionStill.turn())}
209
- </>
210
- );
211
- }
212
-
213
- private renderSquare(sq: Square) {
214
- const { x, y } = this.getSquareCoordinates(sq);
215
- return <rect key={sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} fill={this.props.colorset[squareColor(sq)]} />;
216
- }
217
-
218
- private renderHoveredSquare() {
219
- if (!this.state.hoveredSquare) {
220
- return undefined;
221
- }
222
- const { x, y } = this.getSquareCoordinates(this.state.hoveredSquare);
223
- const thickness = Math.max(2, Math.round(HOVER_MARKER_THICKNESS_FACTOR * this.props.squareSize));
224
- const size = this.props.squareSize - thickness;
225
- const color = this.getColorForAnnotation(this.props.interactionMode === 'editArrows' ? this.props.editedArrowColor! : this.props.moveArrowColor);
226
- return <rect className="kokopu-hoveredSquare" x={x + thickness / 2} y={y + thickness / 2} width={size} height={size} stroke={color} strokeWidth={thickness} />;
227
- }
228
-
229
- private renderPiece(positionStill: Position, sq: Square) {
230
- const cp = positionStill.square(sq);
231
- if (cp === '-' || this.state.inhibitedSquare === sq) {
232
- return undefined;
233
- }
234
- const { x, y } = this.getSquareCoordinates(sq);
235
- return <image key={'piece-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
236
- }
237
-
238
- private renderPieceAnimated(motionCursor: number, sq: Square) {
239
- const move = this.props.move!;
240
- let cp = this.props.position.square(sq);
241
- if (cp === '-' || move.to() === sq || (move.isEnPassant() && move.enPassantSquare() === sq)) {
242
- return undefined;
243
- }
244
- let { x, y } = this.getSquareCoordinates(sq);
245
- if (sq === move.from()) {
246
- const { x: xTo, y: yTo } = this.getSquareCoordinates(move.to());
247
- x = xTo * motionCursor + x * (1 - motionCursor);
248
- y = yTo * motionCursor + y * (1 - motionCursor);
249
- if (move.isPromotion() && motionCursor > 0.8) {
250
- cp = move.coloredPromotion();
251
- }
252
- }
253
- else if (move.isCastling() && sq === move.rookFrom()) {
254
- const { x: xTo, y: yTo } = this.getSquareCoordinates(move.rookTo());
255
- x = xTo * motionCursor + x * (1 - motionCursor);
256
- y = yTo * motionCursor + y * (1 - motionCursor);
257
- }
258
- return <image key={'piece-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
259
- }
260
-
261
- private renderDraggedPiece(positionStill: Position) {
262
- if ((this.props.interactionMode !== 'movePieces' && this.props.interactionMode !== 'playMoves') || !this.state.draggedSquare) {
263
- return undefined;
264
- }
265
- const cp = positionStill.square(this.state.draggedSquare) as ColoredPiece; // `draggedSquare` cannot correspond to an empty square
266
- let { x, y } = this.getSquareCoordinates(this.state.draggedSquare);
267
- x = Math.min(Math.max(x + this.state.dragPosition!.x, 0), 7 * this.props.squareSize);
268
- y = Math.min(Math.max(y + this.state.dragPosition!.y, 0), 7 * this.props.squareSize);
269
- return <image className="kokopu-pieceDraggable kokopu-dragging" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
270
- }
271
-
272
- private renderDraggedArrow() {
273
- if (this.props.interactionMode !== 'editArrows' || !this.state.draggedSquare) {
274
- return undefined;
275
- }
276
- const { x, y } = this.getSquareCoordinates(this.state.draggedSquare);
277
- const xFrom = x + this.props.squareSize / 2;
278
- const yFrom = y + this.props.squareSize / 2;
279
- const xTo = Math.min(Math.max(x + this.state.dragPosition!.x + this.state.cursorOffset!.x, this.props.squareSize / 2), 15 * this.props.squareSize / 2);
280
- const yTo = Math.min(Math.max(y + this.state.dragPosition!.y + this.state.cursorOffset!.y, this.props.squareSize / 2), 15 * this.props.squareSize / 2);
281
- if (xFrom === xTo && yFrom === yTo) {
282
- return undefined;
283
- }
284
- const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
285
- const editArrowColor = this.props.editedArrowColor!;
286
- return (
287
- <line
288
- className="kokopu-annotation kokopu-arrow kokopu-arrowDraggable kokopu-dragging" x1={xFrom} y1={yFrom} x2={xTo} y2={yTo}
289
- strokeWidth={strokeWidth} stroke={this.getColorForAnnotation(editArrowColor)} markerEnd={`url(#${this.getArrowTipId(editArrowColor)})`}
290
- />
291
- );
292
- }
293
-
294
- private renderPromotionDrawer(positionStill: Position) {
295
- if (!this.state.promotionDrawer) {
296
- return undefined;
297
- }
298
- const { x, y } = this.getSquareCoordinates(this.state.promotionDrawer.origin);
299
- const inverted = positionStill.turn() === (this.props.flipped ? 'w' : 'b'); // false==top-to-bottom true==bottom-to-top
300
- const y0 = inverted ? y - this.props.squareSize * (this.state.promotionDrawer.buttons.length - 1) : y;
301
- const buttons = this.state.promotionDrawer.buttons.map((p, i) => {
302
- const cp = (positionStill.turn() + p) as ColoredPiece;
303
- return (
304
- <image
305
- key={'drawer-piece-' + p} className="kokopu-clickable" x={x} y={y + i * (inverted ? -this.props.squareSize : this.props.squareSize)}
306
- width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} onClick={() => this.handleDrawerButtonClicked(p)}
307
- />
308
- );
309
- });
310
- return (
311
- <>
312
- <rect className="kokopu-handle" x={0} y={0} width={this.props.squareSize * 8} height={this.props.squareSize * 8}
313
- onClick={() => this.handleDrawerCancelButtonClicked()} />
314
- <rect x={x} y={y0} width={this.props.squareSize} height={this.props.squareSize * this.state.promotionDrawer.buttons.length}
315
- fill={this.props.colorset.b} />
316
- <rect x={x} y={y0} width={this.props.squareSize} height={this.props.squareSize * this.state.promotionDrawer.buttons.length}
317
- className="kokopu-drawerMask" fill={this.props.colorset.w} />
318
- {buttons}
319
- </>
320
- );
321
- }
322
-
323
- private renderSquareHandle(positionStill: Position, sq: Square) {
324
- const { x, y } = this.getSquareCoordinates(sq);
325
- const dragEnabledForMovePieces = this.props.interactionMode === 'movePieces' && positionStill.square(sq) !== '-';
326
- const dragEnabledForEditArrows = this.props.interactionMode === 'editArrows';
327
- const dragEnabledForPlayMoves = this.props.interactionMode === 'playMoves' && !this.state.promotionDrawer && positionStill.isLegal() &&
328
- positionStill.square(sq).startsWith(positionStill.turn());
329
- if (dragEnabledForMovePieces || dragEnabledForEditArrows || dragEnabledForPlayMoves) {
330
- return (
331
- <Draggable
332
- key={'handle-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} isArrowHandle={dragEnabledForEditArrows}
333
- onDragStart={(x0, y0) => this.handleDragStart(sq, x0, y0)}
334
- onDrag={(dx, dy) => this.handleDrag(sq, dx, dy)}
335
- onDragStop={(dx, dy) => this.handleDragStop(sq, dx, dy)}
336
- onDragCanceled={() => this.handleDragCanceled()}
337
- />
338
- );
339
- }
340
- else if (this.props.interactionMode === 'clickSquares') {
341
- return (
342
- <rect
343
- key={'handle-' + sq} className="kokopu-handle kokopu-clickable" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize}
344
- onClick={() => this.handleSquareClicked(sq)}
345
- />
346
- );
347
- }
348
- else {
349
- return undefined;
350
- }
351
- }
352
-
353
- private renderSquareMarkers() {
354
- if (!this.props.squareMarkers) {
355
- return undefined;
356
- }
357
- return Object.entries(this.props.squareMarkers).map(([ sq, color ]) => {
358
- const { x, y } = this.getSquareCoordinates(sq as Square);
359
- return (
360
- <rect key={'sqm-' + sq} className="kokopu-annotation" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize}
361
- fill={this.getColorForAnnotation(color)}
362
- />
363
- );
364
- });
365
- }
366
-
367
- private renderTextMarkers() {
368
- if (!this.props.textMarkers) {
369
- return undefined;
370
- }
371
- return Object.entries(this.props.textMarkers).map(([ sq, marker ]) => {
372
- let { x, y } = this.getSquareCoordinates(sq as Square);
373
- x += this.props.squareSize / 2;
374
- y += this.props.squareSize / 2;
375
- return (
376
- <g key={'txtm-' + sq} className="kokopu-annotation">
377
- <AnnotationSymbolShape x={x} y={y} size={this.props.squareSize} symbol={marker.symbol} color={this.getColorForAnnotation(marker.color)} />
378
- </g>
379
- );
380
- });
381
- }
382
-
383
- private renderArrowMarkers() {
384
- if (!this.props.arrowMarkers) {
385
- return undefined;
386
- }
387
- const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
388
- return Object.entries(this.props.arrowMarkers).map(([ vect, color ]) => {
389
- const from = vect.substring(0, 2) as Square;
390
- const to = vect.substring(2, 4) as Square;
391
- if (from === to) {
392
- return undefined;
393
- }
394
- let { x: xFrom, y: yFrom } = this.getSquareCoordinates(from);
395
- let { x: xTo, y: yTo } = this.getSquareCoordinates(to);
396
- xFrom += this.props.squareSize / 2;
397
- yFrom += this.props.squareSize / 2;
398
- xTo += this.props.squareSize / 2;
399
- yTo += this.props.squareSize / 2;
400
- xTo += Math.sign(xFrom - xTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
401
- yTo += Math.sign(yFrom - yTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
402
- return (
403
- <line
404
- key={'arm-' + vect} className="kokopu-annotation kokopu-arrow" x1={xFrom} y1={yFrom} x2={xTo} y2={yTo} strokeWidth={strokeWidth}
405
- stroke={this.getColorForAnnotation(color)} markerEnd={`url(#${this.getArrowTipId(color)})`}
406
- />
407
- );
408
- });
409
- }
410
-
411
- private renderMoveArrow(motionCursor: number) {
412
- if (!this.props.move || motionCursor < 0.1 || !this.props.moveArrowVisible || this.props.move.from() === this.props.move.to()) {
413
- return undefined;
414
- }
415
- const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
416
- let { x: xFrom, y: yFrom } = this.getSquareCoordinates(this.props.move.from());
417
- xFrom += this.props.squareSize / 2;
418
- yFrom += this.props.squareSize / 2;
419
- let { x: xTo, y: yTo } = this.getSquareCoordinates(this.props.move.to());
420
- xTo += this.props.squareSize / 2;
421
- yTo += this.props.squareSize / 2;
422
- xTo += Math.sign(xFrom - xTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
423
- yTo += Math.sign(yFrom - yTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
424
- const x = xTo * motionCursor + xFrom * (1 - motionCursor);
425
- const y = yTo * motionCursor + yFrom * (1 - motionCursor);
426
- return (
427
- <line
428
- className="kokopu-annotation kokopu-arrow" x1={xFrom} y1={yFrom} x2={x} y2={y} strokeWidth={strokeWidth}
429
- stroke={this.getColorForAnnotation(this.props.moveArrowColor)} markerEnd={`url(#${this.getArrowTipId(this.props.moveArrowColor)})`}
430
- />
431
- );
432
- }
433
-
434
- private renderArrowTip(color: AnnotationColor) {
435
- return <ArrowTip id={this.getArrowTipId(color)} color={this.getColorForAnnotation(color)} />;
436
- }
437
-
438
- private renderTurnFlag(turn: Color) {
439
- if (!this.props.turnVisible) {
440
- return undefined;
441
- }
442
- const x = 8 * this.props.squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * this.props.squareSize);
443
- const y = turn === (this.props.flipped ? 'b' : 'w') ? 7 * this.props.squareSize : 0;
444
- return <image key={'turn-' + turn} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.getURLForTurnFlag(turn)} />;
445
- }
446
-
447
- private renderRankCoordinate(fontSize: number, rank: number) {
448
- const x = Math.round(-RANK_COORDINATE_WIDTH_FACTOR * fontSize) / 2;
449
- const y = (this.props.flipped ? rank + 0.5 : 7.5 - rank) * this.props.squareSize;
450
- const label = RANK_LABELS[rank];
451
- return <text key={'rank-' + label} className="kokopu-coordinate" x={x} y={y} fontSize={fontSize}>{label}</text>;
452
- }
453
-
454
- private renderFileCoordinate(fontSize: number, file: number) {
455
- const x = (this.props.flipped ? 7.5 - file : 0.5 + file) * this.props.squareSize;
456
- const y = 8 * this.props.squareSize + Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize) / 2;
457
- const label = FILE_LABELS[file];
458
- return <text key={'file-' + label} className="kokopu-coordinate" x={x} y={y} fontSize={fontSize}>{label}</text>;
459
- }
460
-
461
- private handleDragStart(sq: Square, x0: number, y0: number) {
462
- this.setState({
463
- inhibitedSquare: this.props.interactionMode === 'movePieces' || this.props.interactionMode === 'playMoves' ? sq : undefined,
464
- draggedSquare: sq,
465
- hoveredSquare: sq,
466
- cursorOffset: { x: x0, y: y0 },
467
- dragPosition: { x: 0, y: 0 },
468
- });
469
- }
470
-
471
- private handleDrag(sq: Square, dx: number, dy: number) {
472
- const { x, y } = this.getSquareCoordinates(sq);
473
- const targetSq = this.getSquareAt(x + dx + this.state.cursorOffset!.x, y + dy + this.state.cursorOffset!.y);
474
- this.setState({
475
- hoveredSquare: targetSq,
476
- dragPosition: { x: dx, y: dy },
477
- });
478
- }
479
-
480
- private handleDragStop(sq: Square, dx: number, dy: number) {
481
- const { x, y } = this.getSquareCoordinates( sq);
482
- const targetSq = this.getSquareAt(x + dx + this.state.cursorOffset!.x, y + dy + this.state.cursorOffset!.y);
483
- this.setState({
484
- inhibitedSquare: undefined,
485
- draggedSquare: undefined,
486
- hoveredSquare: undefined,
487
- });
488
- if (sq === targetSq || !targetSq) {
489
- return;
490
- }
491
- if (this.props.interactionMode === 'movePieces' && this.props.onPieceMoved) {
492
- this.props.onPieceMoved(sq, targetSq);
493
- }
494
- else if (this.props.interactionMode === 'editArrows' && this.props.onArrowEdited) {
495
- this.props.onArrowEdited(sq, targetSq);
496
- }
497
- else if (this.props.interactionMode === 'playMoves') {
498
- const positionStill = this.getPositionStill();
499
- const info = positionStill.isMoveLegal(sq, targetSq);
500
- if (!info) {
501
- return;
502
- }
503
- switch (info.status) {
504
-
505
- // Regular move.
506
- case 'regular':
507
- if (this.props.onMovePlayed) {
508
- this.props.onMovePlayed(positionStill.notation(info()));
509
- }
510
- break;
511
-
512
- // Promotion move.
513
- case 'promotion':
514
- this.setState({
515
- inhibitedSquare: sq, // The moving pawn must not be visible while the promotion drawer is opened.
516
- promotionDrawer: {
517
- origin: targetSq,
518
- buttons: positionStill.variant() === 'antichess' ? [ 'q', 'r', 'b', 'n', 'k' ] : [ 'q', 'r', 'b', 'n' ],
519
- builder: piece => positionStill.notation(info(piece)),
520
- },
521
- });
522
- break;
523
-
524
- // Other cases are not supposed to happen.
525
- // istanbul ignore next
526
- default:
527
- break;
528
- }
529
- }
530
- }
531
-
532
- private handleDragCanceled() {
533
- this.setState({
534
- inhibitedSquare: undefined,
535
- draggedSquare: undefined,
536
- hoveredSquare: undefined,
537
- });
538
- }
539
-
540
- private handleDrawerCancelButtonClicked() {
541
- this.setState({
542
- inhibitedSquare: undefined,
543
- promotionDrawer: undefined,
544
- });
545
- }
546
-
547
- private handleDrawerButtonClicked(piece: Piece) {
548
- const builder = this.state.promotionDrawer!.builder;
549
- this.setState({
550
- inhibitedSquare: undefined,
551
- promotionDrawer: undefined,
552
- });
553
- if (this.props.onMovePlayed) {
554
- this.props.onMovePlayed(builder(piece));
555
- }
556
- }
557
-
558
- private handleSquareClicked(sq: Square) {
559
- if (this.props.onSquareClicked) {
560
- this.props.onSquareClicked(sq);
561
- }
562
- }
563
-
564
- /**
565
- * Return the position obtained after playing the move passed among the component props, if any.
566
- */
567
- private getPositionStill() {
568
- if (!this.props.move) {
569
- return this.props.position;
570
- }
571
- const result = new Position(this.props.position);
572
- result.play(this.props.move);
573
- return result;
574
- }
575
-
576
- /**
577
- * Return the (x,y) coordinates of the given square in the SVG canvas.
578
- */
579
- private getSquareCoordinates(sq: Square) {
580
- const { file, rank } = squareToCoordinates(sq);
581
- const x = this.props.flipped ? (7 - file) * this.props.squareSize : file * this.props.squareSize;
582
- const y = this.props.flipped ? rank * this.props.squareSize : (7 - rank) * this.props.squareSize;
583
- return { x: x, y: y };
584
- }
585
-
586
- /**
587
- * Return the square at the given location.
588
- */
589
- private getSquareAt(x: number, y: number) {
590
- const file = this.props.flipped ? 7 - Math.floor(x / this.props.squareSize) : Math.floor(x / this.props.squareSize);
591
- const rank = this.props.flipped ? Math.floor(y / this.props.squareSize) : 7 - Math.floor(y / this.props.squareSize);
592
- return file >= 0 && file < 8 && rank >= 0 && rank < 8 ? coordinatesToSquare(file, rank) : undefined;
593
- }
594
-
595
- /**
596
- * Return the DOM ID of an arrow tip with the given color.
597
- */
598
- private getArrowTipId(color: AnnotationColor) {
599
- return `kokopu-arrowTip-${color}-${this.arrowTipIdSuffix}`;
600
- }
601
-
602
- /**
603
- * Return the [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) to use for the given symbolic annotation color.
604
- */
605
- private getColorForAnnotation(color: AnnotationColor) {
606
- return this.props.colorset[('c' + color) as `c${AnnotationColor}`];
607
- }
608
-
609
- /**
610
- * Return the URL to the image to use for the turn flag of the given color.
611
- */
612
- private getURLForTurnFlag(color: Color) {
613
- return this.props.pieceset[(color + 'x') as `${Color}x`];
614
- }
104
+ private arrowTipIdSuffix: string = generateRandomId();
105
+
106
+ constructor(props: ChessboardImplProps) {
107
+ super(props);
108
+ this.state = {};
109
+ }
110
+
111
+ render() {
112
+
113
+ let fontSize = 0;
114
+
115
+ // Render the squares.
116
+ const squares: React.ReactNode[] = [];
117
+ forEachSquare(sq => squares.push(this.renderSquare(sq)));
118
+
119
+ // Render coordinates.
120
+ const rankCoordinates: React.ReactNode[] = [];
121
+ const fileCoordinates: React.ReactNode[] = [];
122
+ if (this.props.coordinateVisible) {
123
+ fontSize = computeCoordinateFontSize(this.props.squareSize);
124
+ for (let i = 0; i < 8; ++i) {
125
+ rankCoordinates.push(this.renderRankCoordinate(fontSize, i));
126
+ fileCoordinates.push(this.renderFileCoordinate(fontSize, i));
127
+ }
128
+ }
129
+
130
+ // Render the whole canvas.
131
+ const xmin = Math.round(-RANK_COORDINATE_WIDTH_FACTOR * fontSize);
132
+ const ymin = 0;
133
+ const xmax = this.props.turnVisible ? 9 * this.props.squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * this.props.squareSize) : 8 * this.props.squareSize;
134
+ const ymax = 8 * this.props.squareSize + Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
135
+ const viewBox = `${xmin} ${ymin} ${xmax - xmin} ${ymax - ymin}`;
136
+ return (
137
+ <svg className="kokopu-chessboard" viewBox={viewBox} width={xmax - xmin} height={ymax - ymin}>
138
+ <defs>
139
+ {this.renderArrowTip('b')}
140
+ {this.renderArrowTip('g')}
141
+ {this.renderArrowTip('r')}
142
+ {this.renderArrowTip('y')}
143
+ </defs>
144
+ {squares}
145
+ {rankCoordinates}
146
+ {fileCoordinates}
147
+ {this.renderBoardContent()}
148
+ </svg>
149
+ );
150
+ }
151
+
152
+ private renderBoardContent() {
153
+ if (this.props.move && this.props.animated) {
154
+ return (
155
+ <Motion duration={MOTION_DURATION}>
156
+ {motionCursor => (motionCursor === 1 ? this.renderBoardContentStill() : this.renderBoardContentAnimated(motionCursor))}
157
+ </Motion>
158
+ );
159
+ }
160
+ else {
161
+ return this.renderBoardContentStill();
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Render the board content during the animation.
167
+ */
168
+ private renderBoardContentAnimated(motionCursor: number) {
169
+ const pieces: React.ReactNode[] = [];
170
+ forEachSquare(sq => pieces.push(this.renderPieceAnimated(motionCursor, sq)));
171
+ return (
172
+ <>
173
+ {pieces}
174
+ {this.renderMoveArrow(motionCursor)}
175
+ {this.renderTurnFlag(oppositeColor(this.props.position.turn()))}
176
+ </>
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Render the board content when the animation has been completed (or if there is no animation).
182
+ */
183
+ private renderBoardContentStill() {
184
+
185
+ const positionStill = this.getPositionStill();
186
+
187
+ // Render the square-related objects.
188
+ const pieces: React.ReactNode[] = [];
189
+ const handles: React.ReactNode[] = [];
190
+ forEachSquare(sq => {
191
+ pieces.push(this.renderPiece(positionStill, sq));
192
+ if (this.props.interactionMode) {
193
+ handles.push(this.renderSquareHandle(positionStill, sq));
194
+ }
195
+ });
196
+ return (
197
+ <>
198
+ {this.renderSquareMarkers()}
199
+ {this.renderHoveredSquare()}
200
+ {pieces}
201
+ {this.renderTextMarkers()}
202
+ {this.renderArrowMarkers()}
203
+ {this.renderMoveArrow(1)}
204
+ {handles}
205
+ {this.renderPromotionDrawer(positionStill)}
206
+ {this.renderDraggedPiece(positionStill)}
207
+ {this.renderDraggedArrow()}
208
+ {this.renderTurnFlag(positionStill.turn())}
209
+ </>
210
+ );
211
+ }
212
+
213
+ private renderSquare(sq: Square) {
214
+ const { x, y } = this.getSquareCoordinates(sq);
215
+ return <rect key={sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} fill={this.props.colorset[squareColor(sq)]} />;
216
+ }
217
+
218
+ private renderHoveredSquare() {
219
+ if (!this.state.hoveredSquare) {
220
+ return undefined;
221
+ }
222
+ const { x, y } = this.getSquareCoordinates(this.state.hoveredSquare);
223
+ const thickness = Math.max(2, Math.round(HOVER_MARKER_THICKNESS_FACTOR * this.props.squareSize));
224
+ const size = this.props.squareSize - thickness;
225
+ const color = this.getColorForAnnotation(this.props.interactionMode === 'editArrows' ? this.props.editedArrowColor! : this.props.moveArrowColor);
226
+ return <rect className="kokopu-hoveredSquare" x={x + thickness / 2} y={y + thickness / 2} width={size} height={size} stroke={color} strokeWidth={thickness} />;
227
+ }
228
+
229
+ private renderPiece(positionStill: Position, sq: Square) {
230
+ const cp = positionStill.square(sq);
231
+ if (cp === '-' || this.state.inhibitedSquare === sq) {
232
+ return undefined;
233
+ }
234
+ const { x, y } = this.getSquareCoordinates(sq);
235
+ return <image key={'piece-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
236
+ }
237
+
238
+ private renderPieceAnimated(motionCursor: number, sq: Square) {
239
+ const move = this.props.move!;
240
+ let cp = this.props.position.square(sq);
241
+ if (cp === '-' || move.to() === sq || (move.isEnPassant() && move.enPassantSquare() === sq)) {
242
+ return undefined;
243
+ }
244
+ let { x, y } = this.getSquareCoordinates(sq);
245
+ if (sq === move.from()) {
246
+ const { x: xTo, y: yTo } = this.getSquareCoordinates(move.to());
247
+ x = xTo * motionCursor + x * (1 - motionCursor);
248
+ y = yTo * motionCursor + y * (1 - motionCursor);
249
+ if (move.isPromotion() && motionCursor > 0.8) {
250
+ cp = move.coloredPromotion();
251
+ }
252
+ }
253
+ else if (move.isCastling() && sq === move.rookFrom()) {
254
+ const { x: xTo, y: yTo } = this.getSquareCoordinates(move.rookTo());
255
+ x = xTo * motionCursor + x * (1 - motionCursor);
256
+ y = yTo * motionCursor + y * (1 - motionCursor);
257
+ }
258
+ return <image key={'piece-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
259
+ }
260
+
261
+ private renderDraggedPiece(positionStill: Position) {
262
+ if ((this.props.interactionMode !== 'movePieces' && this.props.interactionMode !== 'playMoves') || !this.state.draggedSquare) {
263
+ return undefined;
264
+ }
265
+ const cp = positionStill.square(this.state.draggedSquare) as ColoredPiece; // `draggedSquare` cannot correspond to an empty square
266
+ let { x, y } = this.getSquareCoordinates(this.state.draggedSquare);
267
+ x = Math.min(Math.max(x + this.state.dragPosition!.x, 0), 7 * this.props.squareSize);
268
+ y = Math.min(Math.max(y + this.state.dragPosition!.y, 0), 7 * this.props.squareSize);
269
+ return <image className="kokopu-pieceDraggable kokopu-dragging" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} />;
270
+ }
271
+
272
+ private renderDraggedArrow() {
273
+ if (this.props.interactionMode !== 'editArrows' || !this.state.draggedSquare) {
274
+ return undefined;
275
+ }
276
+ const { x, y } = this.getSquareCoordinates(this.state.draggedSquare);
277
+ const xFrom = x + this.props.squareSize / 2;
278
+ const yFrom = y + this.props.squareSize / 2;
279
+ const xTo = Math.min(Math.max(x + this.state.dragPosition!.x + this.state.cursorOffset!.x, this.props.squareSize / 2), 15 * this.props.squareSize / 2);
280
+ const yTo = Math.min(Math.max(y + this.state.dragPosition!.y + this.state.cursorOffset!.y, this.props.squareSize / 2), 15 * this.props.squareSize / 2);
281
+ if (xFrom === xTo && yFrom === yTo) {
282
+ return undefined;
283
+ }
284
+ const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
285
+ const editArrowColor = this.props.editedArrowColor!;
286
+ return (
287
+ <line
288
+ className="kokopu-annotation kokopu-arrow kokopu-arrowDraggable kokopu-dragging" x1={xFrom} y1={yFrom} x2={xTo} y2={yTo}
289
+ strokeWidth={strokeWidth} stroke={this.getColorForAnnotation(editArrowColor)} markerEnd={`url(#${this.getArrowTipId(editArrowColor)})`}
290
+ />
291
+ );
292
+ }
293
+
294
+ private renderPromotionDrawer(positionStill: Position) {
295
+ if (!this.state.promotionDrawer) {
296
+ return undefined;
297
+ }
298
+ const { x, y } = this.getSquareCoordinates(this.state.promotionDrawer.origin);
299
+ const inverted = positionStill.turn() === (this.props.flipped ? 'w' : 'b'); // false==top-to-bottom true==bottom-to-top
300
+ const y0 = inverted ? y - this.props.squareSize * (this.state.promotionDrawer.buttons.length - 1) : y;
301
+ const buttons = this.state.promotionDrawer.buttons.map((p, i) => {
302
+ const cp = (positionStill.turn() + p) as ColoredPiece;
303
+ return (
304
+ <image
305
+ key={'drawer-piece-' + p} className="kokopu-clickable" x={x} y={y + i * (inverted ? -this.props.squareSize : this.props.squareSize)}
306
+ width={this.props.squareSize} height={this.props.squareSize} href={this.props.pieceset[cp]} onClick={() => this.handleDrawerButtonClicked(p)}
307
+ />
308
+ );
309
+ });
310
+ return (
311
+ <>
312
+ <rect className="kokopu-handle" x={0} y={0} width={this.props.squareSize * 8} height={this.props.squareSize * 8}
313
+ onClick={() => this.handleDrawerCancelButtonClicked()} />
314
+ <rect x={x} y={y0} width={this.props.squareSize} height={this.props.squareSize * this.state.promotionDrawer.buttons.length}
315
+ fill={this.props.colorset.b} />
316
+ <rect x={x} y={y0} width={this.props.squareSize} height={this.props.squareSize * this.state.promotionDrawer.buttons.length}
317
+ className="kokopu-drawerMask" fill={this.props.colorset.w} />
318
+ {buttons}
319
+ </>
320
+ );
321
+ }
322
+
323
+ private renderSquareHandle(positionStill: Position, sq: Square) {
324
+ const { x, y } = this.getSquareCoordinates(sq);
325
+ const dragEnabledForMovePieces = this.props.interactionMode === 'movePieces' && positionStill.square(sq) !== '-';
326
+ const dragEnabledForEditArrows = this.props.interactionMode === 'editArrows';
327
+ const dragEnabledForPlayMoves = this.props.interactionMode === 'playMoves' && !this.state.promotionDrawer && positionStill.isLegal() &&
328
+ positionStill.square(sq).startsWith(positionStill.turn());
329
+ if (dragEnabledForMovePieces || dragEnabledForEditArrows || dragEnabledForPlayMoves) {
330
+ return (
331
+ <Draggable
332
+ key={'handle-' + sq} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} isArrowHandle={dragEnabledForEditArrows}
333
+ onDragStart={(x0, y0) => this.handleDragStart(sq, x0, y0)}
334
+ onDrag={(dx, dy) => this.handleDrag(sq, dx, dy)}
335
+ onDragStop={(dx, dy) => this.handleDragStop(sq, dx, dy)}
336
+ onDragCanceled={() => this.handleDragCanceled()}
337
+ />
338
+ );
339
+ }
340
+ else if (this.props.interactionMode === 'clickSquares') {
341
+ return (
342
+ <rect
343
+ key={'handle-' + sq} className="kokopu-handle kokopu-clickable" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize}
344
+ onClick={() => this.handleSquareClicked(sq)}
345
+ />
346
+ );
347
+ }
348
+ else {
349
+ return undefined;
350
+ }
351
+ }
352
+
353
+ private renderSquareMarkers() {
354
+ if (!this.props.squareMarkers) {
355
+ return undefined;
356
+ }
357
+ return Object.entries(this.props.squareMarkers).map(([ sq, color ]) => {
358
+ const { x, y } = this.getSquareCoordinates(sq as Square);
359
+ return (
360
+ <rect key={'sqm-' + sq} className="kokopu-annotation" x={x} y={y} width={this.props.squareSize} height={this.props.squareSize}
361
+ fill={this.getColorForAnnotation(color)}
362
+ />
363
+ );
364
+ });
365
+ }
366
+
367
+ private renderTextMarkers() {
368
+ if (!this.props.textMarkers) {
369
+ return undefined;
370
+ }
371
+ return Object.entries(this.props.textMarkers).map(([ sq, marker ]) => {
372
+ let { x, y } = this.getSquareCoordinates(sq as Square);
373
+ x += this.props.squareSize / 2;
374
+ y += this.props.squareSize / 2;
375
+ return (
376
+ <g key={'txtm-' + sq} className="kokopu-annotation">
377
+ <AnnotationSymbolShape x={x} y={y} size={this.props.squareSize} symbol={marker.symbol} color={this.getColorForAnnotation(marker.color)} />
378
+ </g>
379
+ );
380
+ });
381
+ }
382
+
383
+ private renderArrowMarkers() {
384
+ if (!this.props.arrowMarkers) {
385
+ return undefined;
386
+ }
387
+ const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
388
+ return Object.entries(this.props.arrowMarkers).map(([ vect, color ]) => {
389
+ const from = vect.substring(0, 2) as Square;
390
+ const to = vect.substring(2, 4) as Square;
391
+ if (from === to) {
392
+ return undefined;
393
+ }
394
+ let { x: xFrom, y: yFrom } = this.getSquareCoordinates(from);
395
+ let { x: xTo, y: yTo } = this.getSquareCoordinates(to);
396
+ xFrom += this.props.squareSize / 2;
397
+ yFrom += this.props.squareSize / 2;
398
+ xTo += this.props.squareSize / 2;
399
+ yTo += this.props.squareSize / 2;
400
+ xTo += Math.sign(xFrom - xTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
401
+ yTo += Math.sign(yFrom - yTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
402
+ return (
403
+ <line
404
+ key={'arm-' + vect} className="kokopu-annotation kokopu-arrow" x1={xFrom} y1={yFrom} x2={xTo} y2={yTo} strokeWidth={strokeWidth}
405
+ stroke={this.getColorForAnnotation(color)} markerEnd={`url(#${this.getArrowTipId(color)})`}
406
+ />
407
+ );
408
+ });
409
+ }
410
+
411
+ private renderMoveArrow(motionCursor: number) {
412
+ if (!this.props.move || motionCursor < 0.1 || !this.props.moveArrowVisible || this.props.move.from() === this.props.move.to()) {
413
+ return undefined;
414
+ }
415
+ const strokeWidth = this.props.squareSize * STROKE_THICKNESS_FACTOR;
416
+ let { x: xFrom, y: yFrom } = this.getSquareCoordinates(this.props.move.from());
417
+ xFrom += this.props.squareSize / 2;
418
+ yFrom += this.props.squareSize / 2;
419
+ let { x: xTo, y: yTo } = this.getSquareCoordinates(this.props.move.to());
420
+ xTo += this.props.squareSize / 2;
421
+ yTo += this.props.squareSize / 2;
422
+ xTo += Math.sign(xFrom - xTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
423
+ yTo += Math.sign(yFrom - yTo) * ARROW_TIP_OFFSET_FACTOR * this.props.squareSize;
424
+ const x = xTo * motionCursor + xFrom * (1 - motionCursor);
425
+ const y = yTo * motionCursor + yFrom * (1 - motionCursor);
426
+ return (
427
+ <line
428
+ className="kokopu-annotation kokopu-arrow" x1={xFrom} y1={yFrom} x2={x} y2={y} strokeWidth={strokeWidth}
429
+ stroke={this.getColorForAnnotation(this.props.moveArrowColor)} markerEnd={`url(#${this.getArrowTipId(this.props.moveArrowColor)})`}
430
+ />
431
+ );
432
+ }
433
+
434
+ private renderArrowTip(color: AnnotationColor) {
435
+ return <ArrowTip id={this.getArrowTipId(color)} color={this.getColorForAnnotation(color)} />;
436
+ }
437
+
438
+ private renderTurnFlag(turn: Color) {
439
+ if (!this.props.turnVisible) {
440
+ return undefined;
441
+ }
442
+ const x = 8 * this.props.squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * this.props.squareSize);
443
+ const y = turn === (this.props.flipped ? 'b' : 'w') ? 7 * this.props.squareSize : 0;
444
+ return <image key={'turn-' + turn} x={x} y={y} width={this.props.squareSize} height={this.props.squareSize} href={this.getURLForTurnFlag(turn)} />;
445
+ }
446
+
447
+ private renderRankCoordinate(fontSize: number, rank: number) {
448
+ const x = Math.round(-RANK_COORDINATE_WIDTH_FACTOR * fontSize) / 2;
449
+ const y = (this.props.flipped ? rank + 0.5 : 7.5 - rank) * this.props.squareSize;
450
+ const label = RANK_LABELS[rank];
451
+ return <text key={'rank-' + label} className="kokopu-coordinate" x={x} y={y} fontSize={fontSize}>{label}</text>;
452
+ }
453
+
454
+ private renderFileCoordinate(fontSize: number, file: number) {
455
+ const x = (this.props.flipped ? 7.5 - file : 0.5 + file) * this.props.squareSize;
456
+ const y = 8 * this.props.squareSize + Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize) / 2;
457
+ const label = FILE_LABELS[file];
458
+ return <text key={'file-' + label} className="kokopu-coordinate" x={x} y={y} fontSize={fontSize}>{label}</text>;
459
+ }
460
+
461
+ private handleDragStart(sq: Square, x0: number, y0: number) {
462
+ this.setState({
463
+ inhibitedSquare: this.props.interactionMode === 'movePieces' || this.props.interactionMode === 'playMoves' ? sq : undefined,
464
+ draggedSquare: sq,
465
+ hoveredSquare: sq,
466
+ cursorOffset: { x: x0, y: y0 },
467
+ dragPosition: { x: 0, y: 0 },
468
+ });
469
+ }
470
+
471
+ private handleDrag(sq: Square, dx: number, dy: number) {
472
+ const { x, y } = this.getSquareCoordinates(sq);
473
+ const targetSq = this.getSquareAt(x + dx + this.state.cursorOffset!.x, y + dy + this.state.cursorOffset!.y);
474
+ this.setState({
475
+ hoveredSquare: targetSq,
476
+ dragPosition: { x: dx, y: dy },
477
+ });
478
+ }
479
+
480
+ private handleDragStop(sq: Square, dx: number, dy: number) {
481
+ const { x, y } = this.getSquareCoordinates( sq);
482
+ const targetSq = this.getSquareAt(x + dx + this.state.cursorOffset!.x, y + dy + this.state.cursorOffset!.y);
483
+ this.setState({
484
+ inhibitedSquare: undefined,
485
+ draggedSquare: undefined,
486
+ hoveredSquare: undefined,
487
+ });
488
+ if (sq === targetSq || !targetSq) {
489
+ return;
490
+ }
491
+ if (this.props.interactionMode === 'movePieces' && this.props.onPieceMoved) {
492
+ this.props.onPieceMoved(sq, targetSq);
493
+ }
494
+ else if (this.props.interactionMode === 'editArrows' && this.props.onArrowEdited) {
495
+ this.props.onArrowEdited(sq, targetSq);
496
+ }
497
+ else if (this.props.interactionMode === 'playMoves') {
498
+ const positionStill = this.getPositionStill();
499
+ const info = positionStill.isMoveLegal(sq, targetSq);
500
+ if (!info) {
501
+ return;
502
+ }
503
+ switch (info.status) {
504
+
505
+ // Regular move.
506
+ case 'regular':
507
+ if (this.props.onMovePlayed) {
508
+ this.props.onMovePlayed(positionStill.notation(info()));
509
+ }
510
+ break;
511
+
512
+ // Promotion move.
513
+ case 'promotion':
514
+ this.setState({
515
+ inhibitedSquare: sq, // The moving pawn must not be visible while the promotion drawer is opened.
516
+ promotionDrawer: {
517
+ origin: targetSq,
518
+ buttons: positionStill.variant() === 'antichess' ? [ 'q', 'r', 'b', 'n', 'k' ] : [ 'q', 'r', 'b', 'n' ],
519
+ builder: piece => positionStill.notation(info(piece)),
520
+ },
521
+ });
522
+ break;
523
+
524
+ // Other cases are not supposed to happen.
525
+ // istanbul ignore next
526
+ default:
527
+ break;
528
+ }
529
+ }
530
+ }
531
+
532
+ private handleDragCanceled() {
533
+ this.setState({
534
+ inhibitedSquare: undefined,
535
+ draggedSquare: undefined,
536
+ hoveredSquare: undefined,
537
+ });
538
+ }
539
+
540
+ private handleDrawerCancelButtonClicked() {
541
+ this.setState({
542
+ inhibitedSquare: undefined,
543
+ promotionDrawer: undefined,
544
+ });
545
+ }
546
+
547
+ private handleDrawerButtonClicked(piece: Piece) {
548
+ const builder = this.state.promotionDrawer!.builder;
549
+ this.setState({
550
+ inhibitedSquare: undefined,
551
+ promotionDrawer: undefined,
552
+ });
553
+ if (this.props.onMovePlayed) {
554
+ this.props.onMovePlayed(builder(piece));
555
+ }
556
+ }
557
+
558
+ private handleSquareClicked(sq: Square) {
559
+ if (this.props.onSquareClicked) {
560
+ this.props.onSquareClicked(sq);
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Return the position obtained after playing the move passed among the component props, if any.
566
+ */
567
+ private getPositionStill() {
568
+ if (!this.props.move) {
569
+ return this.props.position;
570
+ }
571
+ const result = new Position(this.props.position);
572
+ result.play(this.props.move);
573
+ return result;
574
+ }
575
+
576
+ /**
577
+ * Return the (x,y) coordinates of the given square in the SVG canvas.
578
+ */
579
+ private getSquareCoordinates(sq: Square) {
580
+ const { file, rank } = squareToCoordinates(sq);
581
+ const x = this.props.flipped ? (7 - file) * this.props.squareSize : file * this.props.squareSize;
582
+ const y = this.props.flipped ? rank * this.props.squareSize : (7 - rank) * this.props.squareSize;
583
+ return { x: x, y: y };
584
+ }
585
+
586
+ /**
587
+ * Return the square at the given location.
588
+ */
589
+ private getSquareAt(x: number, y: number) {
590
+ const file = this.props.flipped ? 7 - Math.floor(x / this.props.squareSize) : Math.floor(x / this.props.squareSize);
591
+ const rank = this.props.flipped ? Math.floor(y / this.props.squareSize) : 7 - Math.floor(y / this.props.squareSize);
592
+ return file >= 0 && file < 8 && rank >= 0 && rank < 8 ? coordinatesToSquare(file, rank) : undefined;
593
+ }
594
+
595
+ /**
596
+ * Return the DOM ID of an arrow tip with the given color.
597
+ */
598
+ private getArrowTipId(color: AnnotationColor) {
599
+ return `kokopu-arrowTip-${color}-${this.arrowTipIdSuffix}`;
600
+ }
601
+
602
+ /**
603
+ * Return the [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) to use for the given symbolic annotation color.
604
+ */
605
+ private getColorForAnnotation(color: AnnotationColor) {
606
+ return this.props.colorset[('c' + color) as `c${AnnotationColor}`];
607
+ }
608
+
609
+ /**
610
+ * Return the URL to the image to use for the turn flag of the given color.
611
+ */
612
+ private getURLForTurnFlag(color: Color) {
613
+ return this.props.pieceset[(color + 'x') as `${Color}x`];
614
+ }
615
615
  }
616
616
 
617
617
 
@@ -619,17 +619,17 @@ export class ChessboardImpl extends React.Component<ChessboardImplProps, Chessbo
619
619
  * Return the size of the chessboard, assuming it is built with the given attributes.
620
620
  */
621
621
  export function chessboardSize(squareSize: number, coordinateVisible: boolean, turnVisible: boolean) {
622
- let width = 8 * squareSize;
623
- let height = 8 * squareSize;
624
- if (turnVisible) {
625
- width += squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * squareSize);
626
- }
627
- if (coordinateVisible) {
628
- const fontSize = computeCoordinateFontSize(squareSize);
629
- width += Math.round(RANK_COORDINATE_WIDTH_FACTOR * fontSize);
630
- height += Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
631
- }
632
- return { width: width, height: height };
622
+ let width = 8 * squareSize;
623
+ let height = 8 * squareSize;
624
+ if (turnVisible) {
625
+ width += squareSize + Math.round(TURN_FLAG_SPACING_FACTOR * squareSize);
626
+ }
627
+ if (coordinateVisible) {
628
+ const fontSize = computeCoordinateFontSize(squareSize);
629
+ width += Math.round(RANK_COORDINATE_WIDTH_FACTOR * fontSize);
630
+ height += Math.round(FILE_COORDINATE_HEIGHT_FACTOR * fontSize);
631
+ }
632
+ return { width: width, height: height };
633
633
  }
634
634
 
635
635
 
@@ -637,5 +637,5 @@ export function chessboardSize(squareSize: number, coordinateVisible: boolean, t
637
637
  * Return the font size to use for coordinates assuming the given square size.
638
638
  */
639
639
  function computeCoordinateFontSize(squareSize: number) {
640
- return squareSize <= 32 ? 8 : 8 + (squareSize - 32) * 0.2;
640
+ return squareSize <= 32 ? 8 : 8 + (squareSize - 32) * 0.2;
641
641
  }