kokopu-react 3.1.0 → 3.3.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 (236) hide show
  1. package/.eslintrc.yml +1 -1
  2. package/CHANGELOG.md +10 -0
  3. package/dist/lib/chessboard/BoardProperties.js.map +1 -1
  4. package/dist/lib/chessboard/Chessboard.css +13 -13
  5. package/dist/lib/chessboard/Chessboard.d.ts +27 -2
  6. package/dist/lib/chessboard/Chessboard.js +36 -17
  7. package/dist/lib/chessboard/Chessboard.js.map +1 -1
  8. package/dist/lib/chessboard/ChessboardImpl.js.map +1 -1
  9. package/dist/lib/chessboard/Draggable.js.map +1 -1
  10. package/dist/lib/chessboard/Motion.js.map +1 -1
  11. package/dist/lib/chessboard/colorsets.js.map +1 -1
  12. package/dist/lib/chessboard/piecesets.js.map +1 -1
  13. package/dist/lib/errorbox/ErrorBox.css +18 -18
  14. package/dist/lib/errorbox/ErrorBox.js.map +1 -1
  15. package/dist/lib/errorbox/parsing.js.map +1 -1
  16. package/dist/lib/exception.js.map +1 -1
  17. package/dist/lib/i18n.d.ts +1 -0
  18. package/dist/lib/i18n.js +1 -0
  19. package/dist/lib/i18n.js.map +1 -1
  20. package/dist/lib/icons/AnnotationSymbolShape.css +6 -6
  21. package/dist/lib/icons/AnnotationSymbolShape.js.map +1 -1
  22. package/dist/lib/icons/ArrowMarkerIcon.js.map +1 -1
  23. package/dist/lib/icons/ArrowTip.js.map +1 -1
  24. package/dist/lib/icons/ChessPieceIcon.js.map +1 -1
  25. package/dist/lib/icons/SquareMarkerIcon.js.map +1 -1
  26. package/dist/lib/icons/TextMarkerIcon.js.map +1 -1
  27. package/dist/lib/icons/arrow.css +1 -1
  28. package/dist/lib/index.d.ts +1 -0
  29. package/dist/lib/index.js +1 -0
  30. package/dist/lib/index.js.map +1 -1
  31. package/dist/lib/movetext/Movetext.css +27 -27
  32. package/dist/lib/movetext/Movetext.js.map +1 -1
  33. package/dist/lib/movetext/MovetextImpl.js.map +1 -1
  34. package/dist/lib/movetext/fonts.css +7 -7
  35. package/dist/lib/movetext/htmlFilter.js.map +1 -1
  36. package/dist/lib/movetext/moveFormatter.js.map +1 -1
  37. package/dist/lib/navigationboard/NavigationBoard.d.ts +68 -1
  38. package/dist/lib/navigationboard/NavigationBoard.js +141 -13
  39. package/dist/lib/navigationboard/NavigationBoard.js.map +1 -1
  40. package/dist/lib/navigationboard/NavigationButton.d.ts +66 -0
  41. package/dist/lib/navigationboard/NavigationButton.js +67 -0
  42. package/dist/lib/navigationboard/NavigationButton.js.map +1 -0
  43. package/dist/lib/navigationboard/NavigationField.css +7 -7
  44. package/dist/lib/navigationboard/NavigationField.js.map +1 -1
  45. package/dist/lib/navigationboard/NavigationToolbar.css +16 -13
  46. package/dist/lib/navigationboard/NavigationToolbar.d.ts +10 -10
  47. package/dist/lib/navigationboard/NavigationToolbar.js +61 -79
  48. package/dist/lib/navigationboard/NavigationToolbar.js.map +1 -1
  49. package/dist/lib/navigationboard/iconPaths.d.ts +29 -0
  50. package/dist/lib/navigationboard/iconPaths.js +91 -0
  51. package/dist/lib/navigationboard/iconPaths.js.map +1 -0
  52. package/dist/lib/sanitization.js.map +1 -1
  53. package/dist/lib/types.js.map +1 -1
  54. package/dist/lib/util.js.map +1 -1
  55. package/doc_src/demo/PageChessboardBase.tsx +115 -115
  56. package/doc_src/demo/PageChessboardInteraction.tsx +303 -303
  57. package/doc_src/demo/PageChessboardMove.tsx +173 -173
  58. package/doc_src/demo/PageChessboardSmallScreens.tsx +94 -94
  59. package/doc_src/demo/PageMovetextBase.tsx +180 -180
  60. package/doc_src/demo/PageMovetextInteraction.tsx +123 -123
  61. package/doc_src/demo/PageNavigationBoardBase.tsx +144 -137
  62. package/doc_src/demo/PageNavigationBoardUncontrolledVsControlled.tsx +69 -69
  63. package/doc_src/demo/decs.d.ts +4 -4
  64. package/doc_src/demo/demo.css +12 -12
  65. package/doc_src/demo/util.ts +1 -1
  66. package/doc_src/examples/NavigationBoard.md +1 -1
  67. package/doc_src/theming/LogoRenderer.js +13 -13
  68. package/doc_src/theming/theming.css +5 -5
  69. package/package.json +95 -95
  70. package/scripts/doc.styleguide.config.js +131 -131
  71. package/scripts/doc.tsconfig.json +5 -5
  72. package/scripts/docker-compose.yml +1 -1
  73. package/scripts/test-graphic.tsconfig.json +5 -5
  74. package/scripts/test-graphic.webpack.config.js +57 -57
  75. package/scripts/test-headless.webpack.config.js +29 -29
  76. package/src/chessboard/BoardProperties.ts +68 -68
  77. package/src/chessboard/Chessboard.css +13 -13
  78. package/src/chessboard/Chessboard.tsx +436 -380
  79. package/src/chessboard/ChessboardImpl.tsx +557 -557
  80. package/src/chessboard/Draggable.tsx +185 -185
  81. package/src/chessboard/Motion.tsx +67 -67
  82. package/src/chessboard/colorsets.ts +66 -66
  83. package/src/chessboard/piecesets.ts +84 -84
  84. package/src/decs.d.ts +2 -2
  85. package/src/errorbox/ErrorBox.css +18 -18
  86. package/src/errorbox/ErrorBox.tsx +75 -75
  87. package/src/errorbox/parsing.tsx +95 -95
  88. package/src/exception.ts +17 -17
  89. package/src/i18n.ts +1 -0
  90. package/src/icons/AnnotationSymbolShape.css +6 -6
  91. package/src/icons/AnnotationSymbolShape.tsx +31 -31
  92. package/src/icons/ArrowMarkerIcon.tsx +34 -34
  93. package/src/icons/ArrowTip.tsx +14 -14
  94. package/src/icons/ChessPieceIcon.tsx +45 -45
  95. package/src/icons/SquareMarkerIcon.tsx +20 -20
  96. package/src/icons/TextMarkerIcon.tsx +29 -29
  97. package/src/icons/arrow.css +1 -1
  98. package/src/index.ts +3 -2
  99. package/src/movetext/Movetext.css +27 -27
  100. package/src/movetext/Movetext.tsx +158 -158
  101. package/src/movetext/MovetextImpl.tsx +345 -345
  102. package/src/movetext/fonts.css +7 -7
  103. package/src/movetext/htmlFilter.tsx +129 -129
  104. package/src/movetext/moveFormatter.tsx +37 -37
  105. package/src/navigationboard/NavigationBoard.tsx +386 -180
  106. package/src/navigationboard/NavigationButton.ts +114 -0
  107. package/src/navigationboard/NavigationField.css +7 -7
  108. package/src/navigationboard/NavigationField.tsx +90 -90
  109. package/src/navigationboard/NavigationToolbar.css +16 -13
  110. package/src/navigationboard/NavigationToolbar.tsx +98 -112
  111. package/src/navigationboard/iconPaths.ts +98 -0
  112. package/src/sanitization.ts +17 -17
  113. package/src/types.ts +116 -116
  114. package/src/util.ts +11 -11
  115. package/test/01_types.js +167 -167
  116. package/test/02_chessboard_util.js +191 -115
  117. package/test/03_error_box_graphic.js +16 -16
  118. package/test/04_icons_graphic.js +32 -32
  119. package/test/05_chessboard_graphic.js +60 -60
  120. package/test/06_chessboard_animation.js +36 -36
  121. package/test/07_chessboard_interaction.js +101 -101
  122. package/test/08_chessboard_play_moves.js +62 -62
  123. package/test/09_chessboard_small_screens.js +32 -32
  124. package/test/10_movetext_graphic.js +32 -32
  125. package/test/11_movetext_interaction.js +100 -100
  126. package/test/12_navigation_board_base.js +151 -0
  127. package/test/13_navigation_board_graphic.js +68 -0
  128. package/test/14_navigation_board_autoplay.js +100 -0
  129. package/test/common/graphic.js +106 -97
  130. package/test/graphic_references/12_navigation_board_base/controlled/default.png +0 -0
  131. package/test/graphic_references/{12_navigation_board/controlled/forbidden_navigation_1.png → 12_navigation_board_base/controlled/forbidden_navigation_0.png} +0 -0
  132. package/test/graphic_references/{12_navigation_board/controlled/invalid_node_id.png → 12_navigation_board_base/controlled/forbidden_navigation_1.png} +0 -0
  133. package/test/graphic_references/{12_navigation_board/controlled/forbidden_navigation_0.png → 12_navigation_board_base/controlled/invalid_node_id.png} +0 -0
  134. package/test/graphic_references/12_navigation_board_base/controlled/no_callback_defined_0.png +0 -0
  135. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/controlled/no_callback_defined_1.png +0 -0
  136. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/controlled/overriding_initial_node_id.png +0 -0
  137. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_1.png → 12_navigation_board_base/controlled/regular_navigation_0.png} +0 -0
  138. package/test/graphic_references/{12_navigation_board/uncontrolled/initial_state_at_specific_id.png → 12_navigation_board_base/controlled/regular_navigation_1.png} +0 -0
  139. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_3.png → 12_navigation_board_base/controlled/regular_navigation_2.png} +0 -0
  140. package/test/graphic_references/{12_navigation_board/controlled/regular_navigation_0.png → 12_navigation_board_base/controlled/regular_navigation_3.png} +0 -0
  141. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_2.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_0.png} +0 -0
  142. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_0.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_1.png} +0 -0
  143. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_1.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_beginning_2.png} +0 -0
  144. package/test/graphic_references/{12_navigation_board/controlled/no_callback_defined_0.png → 12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_0.png} +0 -0
  145. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_1.png +0 -0
  146. package/test/graphic_references/12_navigation_board_base/uncontrolled/forbidden_navigation_at_end_2.png +0 -0
  147. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_at_end.png +0 -0
  148. package/test/graphic_references/12_navigation_board_base/uncontrolled/initial_state_at_specific_id.png +0 -0
  149. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_default.png +0 -0
  150. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_default_with_game_index.png +0 -0
  151. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/initial_state_on_null-move.png +0 -0
  152. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_0.png +0 -0
  153. package/test/graphic_references/12_navigation_board_base/uncontrolled/navigation_with_keys_1.png +0 -0
  154. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_2.png +0 -0
  155. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_3.png +0 -0
  156. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/navigation_with_keys_4.png +0 -0
  157. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_flipping_0.png +0 -0
  158. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_flipping_1.png +0 -0
  159. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_0.png +0 -0
  160. package/test/graphic_references/{12_navigation_board/controlled/default.png → 12_navigation_board_base/uncontrolled/regular_navigation_1.png} +0 -0
  161. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_2.png +0 -0
  162. package/test/graphic_references/12_navigation_board_base/uncontrolled/regular_navigation_3.png +0 -0
  163. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_4.png +0 -0
  164. package/test/graphic_references/{12_navigation_board → 12_navigation_board_base}/uncontrolled/regular_navigation_5.png +0 -0
  165. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_1.png +0 -0
  166. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/error_2.png +0 -0
  167. package/test/graphic_references/13_navigation_board_graphic/additional_buttons/multiple_buttons.png +0 -0
  168. package/test/graphic_references/{12_navigation_board/uncontrolled/regular_navigation_3.png → 13_navigation_board_graphic/additional_buttons/single_button.png} +0 -0
  169. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/custom_1.png +0 -0
  170. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/custom_2.png +0 -0
  171. package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/theme/large.png +0 -0
  172. package/test/graphic_references/13_navigation_board_graphic/theme/small.png +0 -0
  173. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_acknowledging_is-playing_changes.png +0 -0
  174. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_1_move_not_acknowledging_is-playing_changes.png +0 -0
  175. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_acknowledging_node_changes.png +0 -0
  176. package/test/graphic_references/14_navigation_board_autoplay/controlled/after_2_moves_not_acknowledging_node_changes.png +0 -0
  177. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_1.png → 14_navigation_board_autoplay/controlled/after_reaching_the_end.png} +0 -0
  178. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_2.png → 14_navigation_board_autoplay/controlled/already_at_the_end.png} +0 -0
  179. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_is-playing_changes.png +0 -0
  180. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_acknowledging_node_changes.png +0 -0
  181. package/test/graphic_references/{12_navigation_board/uncontrolled/forbidden_navigation_at_end_0.png → 14_navigation_board_autoplay/controlled/initial_state_at_the_end.png} +0 -0
  182. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_close_to_end.png +0 -0
  183. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_is-playing_changes.png +0 -0
  184. package/test/graphic_references/14_navigation_board_autoplay/controlled/initial_state_not_acknowledging_node_changes.png +0 -0
  185. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_after_stop.png +0 -0
  186. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_2_moves_from_beginning_before_stop.png +0 -0
  187. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_after_stop.png +0 -0
  188. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/after_reaching_the_end_before_stop.png +0 -0
  189. package/test/graphic_references/14_navigation_board_autoplay/uncontrolled/initial_state_close_to_end.png +0 -0
  190. package/test/graphic_references/{12_navigation_board/uncontrolled/navigation_with_keys_1.png → 14_navigation_board_autoplay/uncontrolled/initial_state_default.png} +0 -0
  191. package/test/graphic_test_app/03_error_box_graphic/base.tsx +14 -14
  192. package/test/graphic_test_app/03_error_box_graphic/localization.tsx +1 -1
  193. package/test/graphic_test_app/04_icons_graphic/chess_piece.tsx +7 -7
  194. package/test/graphic_test_app/04_icons_graphic/marker.tsx +15 -15
  195. package/test/graphic_test_app/05_chessboard_graphic/annotations.tsx +7 -7
  196. package/test/graphic_test_app/05_chessboard_graphic/base.tsx +6 -6
  197. package/test/graphic_test_app/05_chessboard_graphic/chess_variants.tsx +7 -7
  198. package/test/graphic_test_app/05_chessboard_graphic/flipped.tsx +5 -5
  199. package/test/graphic_test_app/05_chessboard_graphic/move.tsx +12 -12
  200. package/test/graphic_test_app/05_chessboard_graphic/theme.tsx +12 -12
  201. package/test/graphic_test_app/06_chessboard_animation/base_1.tsx +6 -6
  202. package/test/graphic_test_app/06_chessboard_animation/base_2.tsx +6 -6
  203. package/test/graphic_test_app/06_chessboard_animation/base_3.tsx +6 -6
  204. package/test/graphic_test_app/06_chessboard_animation/on_redraw.tsx +16 -16
  205. package/test/graphic_test_app/07_chessboard_interaction/click_squares.tsx +5 -5
  206. package/test/graphic_test_app/07_chessboard_interaction/edit_arrows.tsx +4 -4
  207. package/test/graphic_test_app/07_chessboard_interaction/error.tsx +2 -2
  208. package/test/graphic_test_app/07_chessboard_interaction/move_pieces.tsx +5 -5
  209. package/test/graphic_test_app/08_chessboard_play_moves/base.tsx +8 -8
  210. package/test/graphic_test_app/08_chessboard_play_moves/promotions.tsx +4 -4
  211. package/test/graphic_test_app/09_chessboard_small_screens/base.tsx +9 -9
  212. package/test/graphic_test_app/09_chessboard_small_screens/top_bottom_components.tsx +9 -9
  213. package/test/graphic_test_app/10_movetext_graphic/base.tsx +4 -4
  214. package/test/graphic_test_app/10_movetext_graphic/error.tsx +6 -6
  215. package/test/graphic_test_app/10_movetext_graphic/format_move.tsx +1 -1
  216. package/test/graphic_test_app/10_movetext_graphic/html.tsx +5 -5
  217. package/test/graphic_test_app/10_movetext_graphic/options.tsx +10 -10
  218. package/test/graphic_test_app/11_movetext_interaction/error.tsx +1 -1
  219. package/test/graphic_test_app/11_movetext_interaction/select_moves.tsx +11 -11
  220. package/test/graphic_test_app/{12_navigation_board → 12_navigation_board_base}/controlled.tsx +5 -5
  221. package/test/graphic_test_app/{12_navigation_board → 12_navigation_board_base}/uncontrolled.tsx +7 -7
  222. package/test/graphic_test_app/13_navigation_board_graphic/additional_buttons.tsx +49 -0
  223. package/test/graphic_test_app/{12_navigation_board → 13_navigation_board_graphic}/error.tsx +2 -2
  224. package/test/graphic_test_app/{12_navigation_board → 13_navigation_board_graphic}/theme.tsx +4 -4
  225. package/test/graphic_test_app/14_navigation_board_autoplay/controlled.tsx +73 -0
  226. package/test/graphic_test_app/14_navigation_board_autoplay/uncontrolled.tsx +34 -0
  227. package/test/graphic_test_app/common/decs.d.ts +2 -2
  228. package/test/graphic_test_app/common/test_app.css +15 -15
  229. package/test/graphic_test_app/common/test_app.tsx +48 -48
  230. package/tsconfig.json +13 -13
  231. package/test/12_navigation_board.js +0 -168
  232. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_2.png +0 -0
  233. package/test/graphic_references/12_navigation_board/theme/small.png +0 -0
  234. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_1.png +0 -0
  235. /package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/error/pgn_parsing_error.png +0 -0
  236. /package/test/graphic_references/{12_navigation_board → 13_navigation_board_graphic}/error/wrong_game_type.png +0 -0
@@ -29,7 +29,7 @@ import { MoveDescriptor, Position, Square, isSquare, isSquareCouple } from 'koko
29
29
  import { IllegalArgument } from '../exception';
30
30
  import { sanitizeString, sanitizeBoolean, sanitizePartialObject, sanitizeInteger, sanitizeBoundedInteger, sanitizeOptional } from '../sanitization';
31
31
  import { MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, Colorset, Pieceset, AnnotationColor, AnnotationSymbol, SquareMarkerSet, TextMarkerSet, ArrowMarkerSet,
32
- isAnnotationColor, isAnnotationSymbol, parseSquareMarkers, parseTextMarkers, parseArrowMarkers } from '../types';
32
+ isAnnotationColor, isAnnotationSymbol, parseSquareMarkers, parseTextMarkers, parseArrowMarkers } from '../types';
33
33
 
34
34
  import { StaticBoardGraphicProps, DynamicBoardGraphicProps, SmallScreenLimit, DEFAULT_SQUARE_SIZE, defaultDynamicBoardProps } from './BoardProperties';
35
35
  import { colorsets } from './colorsets';
@@ -40,97 +40,125 @@ import { parsePosition, parseMove } from '../errorbox/parsing';
40
40
 
41
41
  export interface ChessboardProps extends DynamicBoardGraphicProps {
42
42
 
43
- /**
44
- * Displayed position. Can be a [kokopu.Position](https://kokopu.yo35.org/docs/current/classes/Position.html) object,
45
- * a [FEN string](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation),
46
- * `'start'` (usual starting position), or `'empty'` (empty board).
47
- *
48
- * Optionally, the FEN string can be prefixed with `'variant:'`, `variant` corresponding to one of the
49
- * [game variant](https://kokopu.yo35.org/docs/current/types/GameVariant.html) supported by Kokopu. For instance:
50
- * `'chess960:nrkbqrbn/pppppppp/8/8/8/8/PPPPPPPP/NRKBQRBN w KQkq - 0 1'`.
51
- */
52
- position: Position | string;
53
-
54
- /**
55
- * Displayed move (optional), defined either as a [kokopu.MoveDescriptor](https://kokopu.yo35.org/docs/current/classes/MoveDescriptor.html) object
56
- * or as a [SAN string](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)) (e.g. `'Nf3'`). Use `'--'` for a null-move.
57
- * In all cases, the move must be a legal move in position defined in attribute `position`.
58
- */
59
- move?: MoveDescriptor | string;
60
-
61
- /**
62
- * Square markers, defined as a {@link SquareMarkerSet} (e.g. `{ e4: 'g', d5: 'r' }`) or as a comma-separated CSL string (e.g. `'Rd5,Ge4'`).
63
- */
64
- squareMarkers?: SquareMarkerSet | string;
65
-
66
- /**
67
- * Text markers, defined as a {@link TextMarkerSet} (e.g. `{ e4: { symbol: 'A', color: 'g' }, d5: { symbol: 'z', color: 'r' }}`)
68
- * or as a comma-separated CTL string (e.g. `'Rzd5,GAe4'`).
69
- */
70
- textMarkers?: TextMarkerSet | string;
71
-
72
- /**
73
- * Arrow markers, defined as a {@link ArrowMarkerSet} (e.g. `{ e2e4: 'g', g8f6: 'r', g8h6: 'y' }`)
74
- * or as a comma-separated CAL string (e.g. `'Ge2e4,Rg8f6,Yg8h6'`).
75
- */
76
- arrowMarkers?: ArrowMarkerSet | string;
77
-
78
- /**
79
- * Whether the board is flipped (i.e. seen from Black's point of view) or not.
80
- */
81
- flipped: boolean;
82
-
83
- /**
84
- * Type of action allowed with the mouse on the chessboard. If undefined, then the user cannot interact with the component.
85
- *
86
- * - `'movePieces'` allows the user to drag & drop the chess pieces from one square to another (regardless of the chess rules),
87
- * - `'clickSquares'` allows the user to click on squares,
88
- * - `'editArrows'` allows the user to draw arrow markers from one square to another (warning: attribute `editedArrowColor` must be set),
89
- * - `'playMoves'` allows the user to play legal chess moves (thus no interaction is possible if the displayed position is not legal).
90
- */
91
- interactionMode?: 'movePieces' | 'clickSquares' | 'editArrows' | 'playMoves',
92
-
93
- /**
94
- * Color of the edited arrow. Mandatory if `interactionMode === 'editArrows'`, ignored otherwise.
95
- */
96
- editedArrowColor?: AnnotationColor;
97
-
98
- /**
99
- * Callback invoked when a piece is moved through drag&drop (only if `interactionMode` is set to `'movePieces'`).
100
- */
101
- onPieceMoved?: (from: Square, to: Square) => void;
102
-
103
- /**
104
- * Callback invoked when the user clicks on a square (only if `interactionMode` is set to `'clickSquares'`).
105
- */
106
- onSquareClicked?: (square: Square) => void;
107
-
108
- /**
109
- * Callback invoked when an arrow is dragged (only if `interactionMode` is set to `'editArrows'`).
110
- */
111
- onArrowEdited?: (from: Square, to: Square) => void;
112
-
113
- /**
114
- * Callback invoked when a move is played (only if `interactionMode` is set to `'playMoves'`).
115
- */
116
- onMovePlayed?: (move: string) => void;
117
-
118
- /**
119
- * Optional component, to be rendered above the chessboard, and customized with the same square-size / coordinate-visible / turn-visible
120
- * parameter values as actually used for the chessboard (which may be different from what is defined in props because of small-screen limits).
121
- */
122
- topComponent?: (attr: Pick<StaticBoardGraphicProps, 'squareSize' | 'coordinateVisible' | 'turnVisible'>) => JSX.Element;
123
-
124
- /**
125
- * Optional component, to be rendered below the chessboard, and customized with the same square-size / coordinate-visible / turn-visible
126
- * parameter values as actually used for the chessboard (which may be different from what is defined in props because of small-screen limits).
127
- */
128
- bottomComponent?: (attr: Pick<StaticBoardGraphicProps, 'squareSize' | 'coordinateVisible' | 'turnVisible'>) => JSX.Element;
43
+ /**
44
+ * Displayed position. Can be a [kokopu.Position](https://kokopu.yo35.org/docs/current/classes/Position.html) object,
45
+ * a [FEN string](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation),
46
+ * `'start'` (usual starting position), or `'empty'` (empty board).
47
+ *
48
+ * Optionally, the FEN string can be prefixed with `'variant:'`, `variant` corresponding to one of the
49
+ * [game variant](https://kokopu.yo35.org/docs/current/types/GameVariant.html) supported by Kokopu. For instance:
50
+ * `'chess960:nrkbqrbn/pppppppp/8/8/8/8/PPPPPPPP/NRKBQRBN w KQkq - 0 1'`.
51
+ */
52
+ position: Position | string;
53
+
54
+ /**
55
+ * Displayed move (optional), defined either as a [kokopu.MoveDescriptor](https://kokopu.yo35.org/docs/current/classes/MoveDescriptor.html) object
56
+ * or as a [SAN string](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)) (e.g. `'Nf3'`). Use `'--'` for a null-move.
57
+ * In all cases, the move must be a legal move in position defined in attribute `position`.
58
+ */
59
+ move?: MoveDescriptor | string;
60
+
61
+ /**
62
+ * Square markers, defined as a {@link SquareMarkerSet} (e.g. `{ e4: 'g', d5: 'r' }`) or as a comma-separated CSL string (e.g. `'Rd5,Ge4'`).
63
+ */
64
+ squareMarkers?: SquareMarkerSet | string;
65
+
66
+ /**
67
+ * Text markers, defined as a {@link TextMarkerSet} (e.g. `{ e4: { symbol: 'A', color: 'g' }, d5: { symbol: 'z', color: 'r' }}`)
68
+ * or as a comma-separated CTL string (e.g. `'Rzd5,GAe4'`).
69
+ */
70
+ textMarkers?: TextMarkerSet | string;
71
+
72
+ /**
73
+ * Arrow markers, defined as a {@link ArrowMarkerSet} (e.g. `{ e2e4: 'g', g8f6: 'r', g8h6: 'y' }`)
74
+ * or as a comma-separated CAL string (e.g. `'Ge2e4,Rg8f6,Yg8h6'`).
75
+ */
76
+ arrowMarkers?: ArrowMarkerSet | string;
77
+
78
+ /**
79
+ * Whether the board is flipped (i.e. seen from Black's point of view) or not.
80
+ */
81
+ flipped: boolean;
82
+
83
+ /**
84
+ * Type of action allowed with the mouse on the chessboard. If undefined, then the user cannot interact with the component.
85
+ *
86
+ * - `'movePieces'` allows the user to drag & drop the chess pieces from one square to another (regardless of the chess rules),
87
+ * - `'clickSquares'` allows the user to click on squares,
88
+ * - `'editArrows'` allows the user to draw arrow markers from one square to another (warning: attribute `editedArrowColor` must be set),
89
+ * - `'playMoves'` allows the user to play legal chess moves (thus no interaction is possible if the displayed position is not legal).
90
+ */
91
+ interactionMode?: 'movePieces' | 'clickSquares' | 'editArrows' | 'playMoves',
92
+
93
+ /**
94
+ * Color of the edited arrow. Mandatory if `interactionMode === 'editArrows'`, ignored otherwise.
95
+ */
96
+ editedArrowColor?: AnnotationColor;
97
+
98
+ /**
99
+ * Callback invoked when a piece is moved through drag&drop (only if `interactionMode` is set to `'movePieces'`).
100
+ */
101
+ onPieceMoved?: (from: Square, to: Square) => void;
102
+
103
+ /**
104
+ * Callback invoked when the user clicks on a square (only if `interactionMode` is set to `'clickSquares'`).
105
+ */
106
+ onSquareClicked?: (square: Square) => void;
107
+
108
+ /**
109
+ * Callback invoked when an arrow is dragged (only if `interactionMode` is set to `'editArrows'`).
110
+ */
111
+ onArrowEdited?: (from: Square, to: Square) => void;
112
+
113
+ /**
114
+ * Callback invoked when a move is played (only if `interactionMode` is set to `'playMoves'`).
115
+ */
116
+ onMovePlayed?: (move: string) => void;
117
+
118
+ /**
119
+ * Optional component, to be rendered above the chessboard, and customized with the same square-size / coordinate-visible / turn-visible
120
+ * parameter values as actually used for the chessboard (which may be different from what is defined in props because of small-screen limits).
121
+ */
122
+ topComponent?: (attr: Pick<StaticBoardGraphicProps, 'squareSize' | 'coordinateVisible' | 'turnVisible'>) => JSX.Element;
123
+
124
+ /**
125
+ * Optional component, to be rendered below the chessboard, and customized with the same square-size / coordinate-visible / turn-visible
126
+ * parameter values as actually used for the chessboard (which may be different from what is defined in props because of small-screen limits).
127
+ */
128
+ bottomComponent?: (attr: Pick<StaticBoardGraphicProps, 'squareSize' | 'coordinateVisible' | 'turnVisible'>) => JSX.Element;
129
+ }
130
+
131
+
132
+ type AuxilliaryComponentSizeFunction = (attr: Pick<StaticBoardGraphicProps, 'squareSize' | 'coordinateVisible' | 'turnVisible'>) => { width: number, height: number };
133
+
134
+
135
+ /**
136
+ * Attributes for method `Chessboard.size()`.
137
+ */
138
+ export interface ChessboardSizeAttr {
139
+ squareSize?: ChessboardProps['squareSize'];
140
+ coordinateVisible?: ChessboardProps['coordinateVisible'];
141
+ turnVisible?: ChessboardProps['turnVisible'];
142
+ smallScreenLimits?: ChessboardProps['smallScreenLimits'];
143
+ topComponent?: AuxilliaryComponentSizeFunction;
144
+ bottomComponent?: AuxilliaryComponentSizeFunction;
145
+ }
146
+
147
+
148
+ /**
149
+ * Attributes for method `Chessboard.adaptSquareSize()`.
150
+ */
151
+ export interface ChessboardAdaptSquareSizeAttr {
152
+ coordinateVisible?: ChessboardProps['coordinateVisible'];
153
+ turnVisible?: ChessboardProps['turnVisible'];
154
+ smallScreenLimits?: ChessboardProps['smallScreenLimits'];
155
+ topComponent?: AuxilliaryComponentSizeFunction;
156
+ bottomComponent?: AuxilliaryComponentSizeFunction;
129
157
  }
130
158
 
131
159
 
132
160
  interface ChessboardState {
133
- windowWidth: number;
161
+ windowWidth: number;
134
162
  }
135
163
 
136
164
 
@@ -140,232 +168,260 @@ interface ChessboardState {
140
168
  */
141
169
  export class Chessboard extends React.Component<ChessboardProps, ChessboardState> {
142
170
 
143
- static defaultProps: Partial<ChessboardProps> = {
144
- ...defaultDynamicBoardProps(),
145
- position: 'start',
146
- flipped: false,
147
- };
148
-
149
- private windowResizeListener = () => this.handleWindowResize();
150
-
151
- constructor(props: ChessboardProps) {
152
- super(props);
153
- this.state = {
154
- windowWidth: window.innerWidth,
155
- };
156
- }
157
-
158
- componentDidMount() {
159
- window.addEventListener('resize', this.windowResizeListener);
160
- }
161
-
162
- componentWillUnmount() {
163
- window.removeEventListener('resize', this.windowResizeListener);
164
- }
165
-
166
- private handleWindowResize() {
167
- this.setState({ windowWidth: window.innerWidth });
168
- }
169
-
170
- render() {
171
-
172
- // Validate the position and move attributes.
173
- const positionInfo = parsePosition(this.props.position, 'Chessboard');
174
- if (positionInfo.error) {
175
- return positionInfo.errorBox;
176
- }
177
- let position = positionInfo.position;
178
- const moveInfo = parseMove(position, this.props.move, 'Chessboard');
179
- if (moveInfo.error) {
180
- return moveInfo.errorBox;
181
- }
182
- const move = moveInfo.type === 'regular' ? moveInfo.move : undefined;
183
- if (moveInfo.type === 'null-move') {
184
- position = new Position(position);
185
- position.playNullMove();
186
- }
187
-
188
- // Validate the markers
189
- const sqm = parseMarkers(this.props.squareMarkers, 'squareMarkers', parseSquareMarkers, isSquare, isAnnotationColor);
190
- const txtm = parseMarkers(this.props.textMarkers, 'textMarkers', parseTextMarkers, isSquare, isTextMarkerElement);
191
- const arm = parseMarkers(this.props.arrowMarkers, 'arrowMarkers', parseArrowMarkers, isSquareCouple, isAnnotationColor);
192
-
193
- // Validate the appearance attributes.
194
- const flipped = sanitizeBoolean(this.props.flipped);
195
- const squareSize = sanitizeBoundedInteger(this.props.squareSize, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, () => new IllegalArgument('Chessboard', 'squareSize'));
196
- const coordinateVisible = sanitizeBoolean(this.props.coordinateVisible);
197
- const turnVisible = sanitizeBoolean(this.props.turnVisible);
198
- const moveArrowVisible = sanitizeBoolean(this.props.moveArrowVisible);
199
- if (!isAnnotationColor(this.props.moveArrowColor)) {
200
- throw new IllegalArgument('Chessboard', 'moveArrowColor');
201
- }
202
- const animated = sanitizeBoolean(this.props.animated);
203
- const colorset = colorsets[this.props.colorset];
204
- if (!colorset) {
205
- throw new IllegalArgument('Chessboard', 'colorset');
206
- }
207
- const pieceset = piecesets[this.props.pieceset];
208
- if (!pieceset) {
209
- throw new IllegalArgument('Chessboard', 'pieceset');
210
- }
211
-
212
- // Validate and enforce the small-screen limits.
213
- const smallScreenLimits = sanitizeSmallScreenLimits(this.props.smallScreenLimits, () => new IllegalArgument('Chessboard', 'smallScreenLimits'));
214
- const actualSquareSize = computeSquareSizeForSmallScreens(squareSize, smallScreenLimits, this.state.windowWidth);
215
- const actualCoordinateVisible = computeCoordinateVisibleForSmallScreens(coordinateVisible, smallScreenLimits, this.state.windowWidth);
216
- const actualTurnVisible = computeTurnVisibleForSmallScreens(turnVisible, smallScreenLimits, this.state.windowWidth);
217
- const auxilliaryComponentAttr = {
218
- squareSize: actualSquareSize,
219
- coordinateVisible: actualCoordinateVisible,
220
- turnVisible: actualTurnVisible,
221
- };
222
-
223
- // Validate the interaction attributes and the callbacks.
224
- const interactionMode = this.getInteractionModeAndValidateEditedArrowColor();
225
- const { onPieceMoved, onSquareClicked, onArrowEdited, onMovePlayed } = this.props;
226
-
227
- // Build a key so that a new `ChessboardImpl` component is instantiated each time the position or the move changes
228
- // (mandatory to ensure that the move animation works properly and that the internal state of the component remains consistent).
229
- const key = `${position.variant()}|${position.fen()}|${move ? position.notation(move) : ''}`;
230
-
231
- return (<>
232
- {this.props.topComponent ? this.props.topComponent(auxilliaryComponentAttr) : undefined}
233
- <ChessboardImpl key={key}
234
- position={position} move={move} squareMarkers={sqm} textMarkers={txtm} arrowMarkers={arm} flipped={flipped}
235
- squareSize={actualSquareSize} coordinateVisible={actualCoordinateVisible} turnVisible={actualTurnVisible}
236
- moveArrowVisible={moveArrowVisible} moveArrowColor={this.props.moveArrowColor} animated={animated}
237
- colorset={colorset} pieceset={pieceset}
238
- interactionMode={interactionMode} editedArrowColor={this.props.editedArrowColor}
239
- onPieceMoved={onPieceMoved} onSquareClicked={onSquareClicked} onArrowEdited={onArrowEdited} onMovePlayed={onMovePlayed}
240
- />
241
- {this.props.bottomComponent ? this.props.bottomComponent(auxilliaryComponentAttr) : undefined}
242
- </>);
243
- }
244
-
245
- private getInteractionModeAndValidateEditedArrowColor() {
246
- const interactionMode = sanitizeOptional(this.props.interactionMode, sanitizeString);
247
- if (interactionMode === undefined || interactionMode === 'movePieces' || interactionMode === 'clickSquares' || interactionMode === 'playMoves') {
248
- return interactionMode;
249
- }
250
- else if (interactionMode === 'editArrows') {
251
- if (!isAnnotationColor(this.props.editedArrowColor)) {
252
- throw new IllegalArgument('Chessboard', 'editedArrowColor');
253
- }
254
- return interactionMode;
255
- }
256
- else {
257
- throw new IllegalArgument('Chessboard', 'interactionMode');
258
- }
259
- }
260
-
261
- /**
262
- * Return the size of the chessboard, assuming it is built with the given attributes.
263
- */
264
- static size(attr?: Partial<Pick<ChessboardProps, 'squareSize' | 'coordinateVisible' | 'turnVisible' | 'smallScreenLimits'>>): { width: number, height: number } {
265
- let { squareSize, coordinateVisible, turnVisible, smallScreenLimits } = sanitizePartialObject(attr, () => new IllegalArgument('Chessboard.size()', 'attr'));
266
-
267
- // Sanitize the arguments.
268
- squareSize = squareSize === undefined ? DEFAULT_SQUARE_SIZE :
269
- sanitizeBoundedInteger(squareSize, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, () => new IllegalArgument('Chessboard.size()', 'squareSize'));
270
- coordinateVisible = coordinateVisible === undefined ? true : sanitizeBoolean(coordinateVisible);
271
- turnVisible = turnVisible === undefined ? true : sanitizeBoolean(turnVisible);
272
- smallScreenLimits = sanitizeSmallScreenLimits(smallScreenLimits, () => new IllegalArgument('Chessboard.size()', 'smallScreenLimits'));
273
-
274
- // Enforce small-screen limits, if any.
275
- if (typeof window !== 'undefined') {
276
- squareSize = computeSquareSizeForSmallScreens(squareSize, smallScreenLimits, window.innerWidth);
277
- coordinateVisible = computeCoordinateVisibleForSmallScreens(coordinateVisible, smallScreenLimits, window.innerWidth);
278
- turnVisible = computeTurnVisibleForSmallScreens(turnVisible, smallScreenLimits, window.innerWidth);
279
- }
280
-
281
- // Compute the dimensions.
282
- return chessboardSize(squareSize, coordinateVisible, turnVisible);
283
- }
284
-
285
- /**
286
- * Return the maximum square size that would allow the chessboard to fit in a rectangle of size `width x height`.
287
- */
288
- static adaptSquareSize(width: number, height: number, attr?: Partial<Pick<ChessboardProps, 'coordinateVisible' | 'turnVisible' | 'smallScreenLimits'>>): number {
289
- let { coordinateVisible, turnVisible, smallScreenLimits } = sanitizePartialObject(attr, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'attr'));
290
-
291
- // Sanitize the arguments.
292
- width = sanitizeInteger(width, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'width'));
293
- height = sanitizeInteger(height, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'height'));
294
- coordinateVisible = coordinateVisible === undefined ? true : sanitizeBoolean(coordinateVisible);
295
- turnVisible = turnVisible === undefined ? true : sanitizeBoolean(turnVisible);
296
- smallScreenLimits = sanitizeSmallScreenLimits(smallScreenLimits, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'smallScreenLimits'));
297
-
298
- // Enforce small-screen limits, if any.
299
- let maxSquareSize = MAX_SQUARE_SIZE;
300
- if (typeof window !== 'undefined') {
301
- maxSquareSize = computeSquareSizeForSmallScreens(maxSquareSize, smallScreenLimits, window.innerWidth);
302
- coordinateVisible = computeCoordinateVisibleForSmallScreens(coordinateVisible, smallScreenLimits, window.innerWidth);
303
- turnVisible = computeTurnVisibleForSmallScreens(turnVisible, smallScreenLimits, window.innerWidth);
304
- }
305
-
306
- function isAdapted(squareSize: number) {
307
- const { width: actualWidth, height: actualHeight } = chessboardSize(squareSize, coordinateVisible!, turnVisible!);
308
- return actualWidth <= width && actualHeight <= height;
309
- }
310
-
311
- // Check min/max bounds.
312
- if (isAdapted(maxSquareSize)) {
313
- return maxSquareSize;
314
- }
315
- else if (!isAdapted(MIN_SQUARE_SIZE)) {
316
- return MIN_SQUARE_SIZE;
317
- }
318
-
319
- // Dichotomic search, between a (inclusive) and b (exclusive).
320
- let a = MIN_SQUARE_SIZE;
321
- let b = maxSquareSize;
322
- while (a + 1 < b) {
323
- const mid = Math.floor((a + b) / 2);
324
- if (isAdapted(mid)) {
325
- a = mid;
326
- }
327
- else {
328
- b = mid;
329
- }
330
- }
331
- return a;
332
- }
333
-
334
- /**
335
- * Minimum square size (inclusive).
336
- *
337
- * @public
338
- */
339
- static minSquareSize(): number {
340
- return MIN_SQUARE_SIZE;
341
- }
342
-
343
- /**
344
- * Maximum square size (inclusive).
345
- *
346
- * @public
347
- */
348
- static maxSquareSize(): number {
349
- return MAX_SQUARE_SIZE;
350
- }
351
-
352
- /**
353
- * Available colorsets for theming.
354
- *
355
- * @public
356
- */
357
- static colorsets(): Record<string, Colorset> {
358
- return colorsets;
359
- }
360
-
361
- /**
362
- * Available piecesets for theming.
363
- *
364
- * @public
365
- */
366
- static piecesets(): Record<string, Pieceset> {
367
- return piecesets;
368
- }
171
+ static defaultProps: Partial<ChessboardProps> = {
172
+ ...defaultDynamicBoardProps(),
173
+ position: 'start',
174
+ flipped: false,
175
+ };
176
+
177
+ private windowResizeListener = () => this.handleWindowResize();
178
+
179
+ constructor(props: ChessboardProps) {
180
+ super(props);
181
+ this.state = {
182
+ windowWidth: window.innerWidth,
183
+ };
184
+ }
185
+
186
+ componentDidMount() {
187
+ window.addEventListener('resize', this.windowResizeListener);
188
+ }
189
+
190
+ componentWillUnmount() {
191
+ window.removeEventListener('resize', this.windowResizeListener);
192
+ }
193
+
194
+ private handleWindowResize() {
195
+ this.setState({ windowWidth: window.innerWidth });
196
+ }
197
+
198
+ render() {
199
+
200
+ // Validate the position and move attributes.
201
+ const positionInfo = parsePosition(this.props.position, 'Chessboard');
202
+ if (positionInfo.error) {
203
+ return positionInfo.errorBox;
204
+ }
205
+ let position = positionInfo.position;
206
+ const moveInfo = parseMove(position, this.props.move, 'Chessboard');
207
+ if (moveInfo.error) {
208
+ return moveInfo.errorBox;
209
+ }
210
+ const move = moveInfo.type === 'regular' ? moveInfo.move : undefined;
211
+ if (moveInfo.type === 'null-move') {
212
+ position = new Position(position);
213
+ position.playNullMove();
214
+ }
215
+
216
+ // Validate the markers
217
+ const sqm = parseMarkers(this.props.squareMarkers, 'squareMarkers', parseSquareMarkers, isSquare, isAnnotationColor);
218
+ const txtm = parseMarkers(this.props.textMarkers, 'textMarkers', parseTextMarkers, isSquare, isTextMarkerElement);
219
+ const arm = parseMarkers(this.props.arrowMarkers, 'arrowMarkers', parseArrowMarkers, isSquareCouple, isAnnotationColor);
220
+
221
+ // Validate the appearance attributes.
222
+ const flipped = sanitizeBoolean(this.props.flipped);
223
+ const squareSize = sanitizeBoundedInteger(this.props.squareSize, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, () => new IllegalArgument('Chessboard', 'squareSize'));
224
+ const coordinateVisible = sanitizeBoolean(this.props.coordinateVisible);
225
+ const turnVisible = sanitizeBoolean(this.props.turnVisible);
226
+ const moveArrowVisible = sanitizeBoolean(this.props.moveArrowVisible);
227
+ if (!isAnnotationColor(this.props.moveArrowColor)) {
228
+ throw new IllegalArgument('Chessboard', 'moveArrowColor');
229
+ }
230
+ const animated = sanitizeBoolean(this.props.animated);
231
+ const colorset = colorsets[this.props.colorset];
232
+ if (!colorset) {
233
+ throw new IllegalArgument('Chessboard', 'colorset');
234
+ }
235
+ const pieceset = piecesets[this.props.pieceset];
236
+ if (!pieceset) {
237
+ throw new IllegalArgument('Chessboard', 'pieceset');
238
+ }
239
+
240
+ // Validate and enforce the small-screen limits.
241
+ const smallScreenLimits = sanitizeSmallScreenLimits(this.props.smallScreenLimits, () => new IllegalArgument('Chessboard', 'smallScreenLimits'));
242
+ const actualSquareSize = computeSquareSizeForSmallScreens(squareSize, smallScreenLimits, this.state.windowWidth);
243
+ const actualCoordinateVisible = computeCoordinateVisibleForSmallScreens(coordinateVisible, smallScreenLimits, this.state.windowWidth);
244
+ const actualTurnVisible = computeTurnVisibleForSmallScreens(turnVisible, smallScreenLimits, this.state.windowWidth);
245
+ const auxilliaryComponentAttr = {
246
+ squareSize: actualSquareSize,
247
+ coordinateVisible: actualCoordinateVisible,
248
+ turnVisible: actualTurnVisible,
249
+ };
250
+
251
+ // Validate the interaction attributes and the callbacks.
252
+ const interactionMode = this.getInteractionModeAndValidateEditedArrowColor();
253
+ const { onPieceMoved, onSquareClicked, onArrowEdited, onMovePlayed } = this.props;
254
+
255
+ // Build a key so that a new `ChessboardImpl` component is instantiated each time the position or the move changes
256
+ // (mandatory to ensure that the move animation works properly and that the internal state of the component remains consistent).
257
+ const key = `${position.variant()}|${position.fen()}|${move ? position.notation(move) : ''}`;
258
+
259
+ return (<>
260
+ {this.props.topComponent ? this.props.topComponent(auxilliaryComponentAttr) : undefined}
261
+ <ChessboardImpl key={key}
262
+ position={position} move={move} squareMarkers={sqm} textMarkers={txtm} arrowMarkers={arm} flipped={flipped}
263
+ squareSize={actualSquareSize} coordinateVisible={actualCoordinateVisible} turnVisible={actualTurnVisible}
264
+ moveArrowVisible={moveArrowVisible} moveArrowColor={this.props.moveArrowColor} animated={animated}
265
+ colorset={colorset} pieceset={pieceset}
266
+ interactionMode={interactionMode} editedArrowColor={this.props.editedArrowColor}
267
+ onPieceMoved={onPieceMoved} onSquareClicked={onSquareClicked} onArrowEdited={onArrowEdited} onMovePlayed={onMovePlayed}
268
+ />
269
+ {this.props.bottomComponent ? this.props.bottomComponent(auxilliaryComponentAttr) : undefined}
270
+ </>);
271
+ }
272
+
273
+ private getInteractionModeAndValidateEditedArrowColor() {
274
+ const interactionMode = sanitizeOptional(this.props.interactionMode, sanitizeString);
275
+ if (interactionMode === undefined || interactionMode === 'movePieces' || interactionMode === 'clickSquares' || interactionMode === 'playMoves') {
276
+ return interactionMode;
277
+ }
278
+ else if (interactionMode === 'editArrows') {
279
+ if (!isAnnotationColor(this.props.editedArrowColor)) {
280
+ throw new IllegalArgument('Chessboard', 'editedArrowColor');
281
+ }
282
+ return interactionMode;
283
+ }
284
+ else {
285
+ throw new IllegalArgument('Chessboard', 'interactionMode');
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Return the size of the chessboard, assuming it is built with the given attributes.
291
+ */
292
+ static size(attr?: ChessboardSizeAttr): { width: number, height: number } {
293
+ const { squareSize, coordinateVisible, turnVisible, smallScreenLimits, topComponent, bottomComponent } = sanitizePartialObject(attr,
294
+ () => new IllegalArgument('Chessboard.size()', 'attr'));
295
+
296
+ // Sanitize the arguments.
297
+ let actualSquareSize = squareSize === undefined ? DEFAULT_SQUARE_SIZE :
298
+ sanitizeBoundedInteger(squareSize, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, () => new IllegalArgument('Chessboard.size()', 'squareSize'));
299
+ let actualCoordinateVisible = coordinateVisible === undefined ? true : sanitizeBoolean(coordinateVisible);
300
+ let actualTurnVisible = turnVisible === undefined ? true : sanitizeBoolean(turnVisible);
301
+ const actualLimits = sanitizeSmallScreenLimits(smallScreenLimits, () => new IllegalArgument('Chessboard.size()', 'smallScreenLimits'));
302
+
303
+ // Enforce small-screen limits, if any.
304
+ if (typeof window !== 'undefined') {
305
+ actualSquareSize = computeSquareSizeForSmallScreens(actualSquareSize, actualLimits, window.innerWidth);
306
+ actualCoordinateVisible = computeCoordinateVisibleForSmallScreens(actualCoordinateVisible, actualLimits, window.innerWidth);
307
+ actualTurnVisible = computeTurnVisibleForSmallScreens(actualTurnVisible, actualLimits, window.innerWidth);
308
+ }
309
+
310
+ // Compute the dimensions.
311
+ return chessBoardWithAuxilliaryComponentSize(actualSquareSize, actualCoordinateVisible, actualTurnVisible, topComponent, bottomComponent);
312
+ }
313
+
314
+ /**
315
+ * Return the maximum square size that would allow the chessboard to fit in a rectangle of size `width x height`.
316
+ */
317
+ static adaptSquareSize(width: number, height: number, attr?: ChessboardAdaptSquareSizeAttr): number {
318
+ const { coordinateVisible, turnVisible, smallScreenLimits, topComponent, bottomComponent } = sanitizePartialObject(attr,
319
+ () => new IllegalArgument('Chessboard.adaptSquareSize()', 'attr'));
320
+
321
+ // Sanitize the arguments.
322
+ width = sanitizeInteger(width, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'width'));
323
+ height = sanitizeInteger(height, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'height'));
324
+ let actualCoordinateVisible = coordinateVisible === undefined ? true : sanitizeBoolean(coordinateVisible);
325
+ let actualTurnVisible = turnVisible === undefined ? true : sanitizeBoolean(turnVisible);
326
+ const actualLimits = sanitizeSmallScreenLimits(smallScreenLimits, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'smallScreenLimits'));
327
+
328
+ // Enforce small-screen limits, if any.
329
+ let maxSquareSize = MAX_SQUARE_SIZE;
330
+ if (typeof window !== 'undefined') {
331
+ maxSquareSize = computeSquareSizeForSmallScreens(maxSquareSize, actualLimits, window.innerWidth);
332
+ actualCoordinateVisible = computeCoordinateVisibleForSmallScreens(actualCoordinateVisible, actualLimits, window.innerWidth);
333
+ actualTurnVisible = computeTurnVisibleForSmallScreens(actualTurnVisible, actualLimits, window.innerWidth);
334
+ }
335
+
336
+ function isAdapted(squareSize: number) {
337
+ const { width: actualWidth, height: actualHeight } = chessBoardWithAuxilliaryComponentSize(squareSize, actualCoordinateVisible, actualTurnVisible,
338
+ topComponent, bottomComponent);
339
+ return actualWidth <= width && actualHeight <= height;
340
+ }
341
+
342
+ // Check min/max bounds.
343
+ if (isAdapted(maxSquareSize)) {
344
+ return maxSquareSize;
345
+ }
346
+ else if (!isAdapted(MIN_SQUARE_SIZE)) {
347
+ return MIN_SQUARE_SIZE;
348
+ }
349
+
350
+ // Dichotomic search, between a (inclusive) and b (exclusive).
351
+ let a = MIN_SQUARE_SIZE;
352
+ let b = maxSquareSize;
353
+ while (a + 1 < b) {
354
+ const mid = Math.floor((a + b) / 2);
355
+ if (isAdapted(mid)) {
356
+ a = mid;
357
+ }
358
+ else {
359
+ b = mid;
360
+ }
361
+ }
362
+ return a;
363
+ }
364
+
365
+ /**
366
+ * Minimum square size (inclusive).
367
+ *
368
+ * @public
369
+ */
370
+ static minSquareSize(): number {
371
+ return MIN_SQUARE_SIZE;
372
+ }
373
+
374
+ /**
375
+ * Maximum square size (inclusive).
376
+ *
377
+ * @public
378
+ */
379
+ static maxSquareSize(): number {
380
+ return MAX_SQUARE_SIZE;
381
+ }
382
+
383
+ /**
384
+ * Available colorsets for theming.
385
+ *
386
+ * @public
387
+ */
388
+ static colorsets(): Record<string, Colorset> {
389
+ return colorsets;
390
+ }
391
+
392
+ /**
393
+ * Available piecesets for theming.
394
+ *
395
+ * @public
396
+ */
397
+ static piecesets(): Record<string, Pieceset> {
398
+ return piecesets;
399
+ }
400
+ }
401
+
402
+
403
+ function chessBoardWithAuxilliaryComponentSize(squareSize: number, coordinateVisible: boolean, turnVisible: boolean,
404
+ topComponent: AuxilliaryComponentSizeFunction | undefined, bottomComponent: AuxilliaryComponentSizeFunction | undefined): { width: number, height: number } {
405
+
406
+ let { width, height } = chessboardSize(squareSize, coordinateVisible, turnVisible);
407
+ const auxilliaryComponentAttr = {
408
+ squareSize: squareSize,
409
+ coordinateVisible: coordinateVisible,
410
+ turnVisible: turnVisible,
411
+ };
412
+
413
+ if (topComponent) {
414
+ const { width: auxilliaryWidth, height: auxilliaryHeight } = topComponent(auxilliaryComponentAttr);
415
+ width = Math.max(width, auxilliaryWidth);
416
+ height += auxilliaryHeight;
417
+ }
418
+ if (bottomComponent) {
419
+ const { width: auxilliaryWidth, height: auxilliaryHeight } = bottomComponent(auxilliaryComponentAttr);
420
+ width = Math.max(width, auxilliaryWidth);
421
+ height += auxilliaryHeight;
422
+ }
423
+
424
+ return { width: width, height: height };
369
425
  }
370
426
 
371
427
 
@@ -373,8 +429,8 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
373
429
  * Compute the actual square size taking into account the given small-screen limits and window width.
374
430
  */
375
431
  function computeSquareSizeForSmallScreens(squareSize: number, smallScreenLimits: SmallScreenLimit[], windowWidth: number) {
376
- const maximumSquareSize = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.squareSize);
377
- return maximumSquareSize === undefined ? squareSize : Math.min(squareSize, maximumSquareSize);
432
+ const maximumSquareSize = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.squareSize);
433
+ return maximumSquareSize === undefined ? squareSize : Math.min(squareSize, maximumSquareSize);
378
434
  }
379
435
 
380
436
 
@@ -382,8 +438,8 @@ function computeSquareSizeForSmallScreens(squareSize: number, smallScreenLimits:
382
438
  * Compute the actual coordinate visibility attribute taking into account the given small-screen limits and window width.
383
439
  */
384
440
  function computeCoordinateVisibleForSmallScreens(coordinateVisible: boolean, smallScreenLimits: SmallScreenLimit[], windowWidth: number) {
385
- const maximumCoordinateVisible = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.coordinateVisible);
386
- return maximumCoordinateVisible === undefined ? coordinateVisible : coordinateVisible && maximumCoordinateVisible;
441
+ const maximumCoordinateVisible = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.coordinateVisible);
442
+ return maximumCoordinateVisible === undefined ? coordinateVisible : coordinateVisible && maximumCoordinateVisible;
387
443
  }
388
444
 
389
445
 
@@ -391,8 +447,8 @@ function computeCoordinateVisibleForSmallScreens(coordinateVisible: boolean, sma
391
447
  * Compute the actual turn flag visibility attribute taking into account the given small-screen limits and window width.
392
448
  */
393
449
  function computeTurnVisibleForSmallScreens(turnVisible: boolean, smallScreenLimits: SmallScreenLimit[], windowWidth: number) {
394
- const maximumTurnVisible = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.turnVisible);
395
- return maximumTurnVisible === undefined ? turnVisible : turnVisible && maximumTurnVisible;
450
+ const maximumTurnVisible = computeSmallScreenLimits(smallScreenLimits, windowWidth, limit => limit.turnVisible);
451
+ return maximumTurnVisible === undefined ? turnVisible : turnVisible && maximumTurnVisible;
396
452
  }
397
453
 
398
454
 
@@ -400,23 +456,23 @@ function computeTurnVisibleForSmallScreens(turnVisible: boolean, smallScreenLimi
400
456
  * Compute the limit applicable to the given window width.
401
457
  */
402
458
  function computeSmallScreenLimits<T>(smallScreenLimits: SmallScreenLimit[], windowWidth: number, getter: (limit: SmallScreenLimit) => T | undefined): T | undefined {
403
- let bestWidth = Infinity;
404
- let bestAttributeValue: T | undefined = undefined;
405
- for (const limit of smallScreenLimits) {
406
-
407
- // Discard the limits not applicable to the current window width (`windowWidth <= limit.width` must hold)
408
- // and those for which the target attribute is not defined.
409
- if (windowWidth > limit.width || getter(limit) === undefined) {
410
- continue;
411
- }
412
-
413
- // Find the limit with the smallest width among those applicable.
414
- if (limit.width < bestWidth) {
415
- bestWidth = limit.width;
416
- bestAttributeValue = getter(limit);
417
- }
418
- }
419
- return bestAttributeValue;
459
+ let bestWidth = Infinity;
460
+ let bestAttributeValue: T | undefined = undefined;
461
+ for (const limit of smallScreenLimits) {
462
+
463
+ // Discard the limits not applicable to the current window width (`windowWidth <= limit.width` must hold)
464
+ // and those for which the target attribute is not defined.
465
+ if (windowWidth > limit.width || getter(limit) === undefined) {
466
+ continue;
467
+ }
468
+
469
+ // Find the limit with the smallest width among those applicable.
470
+ if (limit.width < bestWidth) {
471
+ bestWidth = limit.width;
472
+ bestAttributeValue = getter(limit);
473
+ }
474
+ }
475
+ return bestAttributeValue;
420
476
  }
421
477
 
422
478
 
@@ -424,34 +480,34 @@ function computeSmallScreenLimits<T>(smallScreenLimits: SmallScreenLimit[], wind
424
480
  * Try to interpret the given object as a list of markers.
425
481
  */
426
482
  function parseMarkers<K extends string, V>(markers: Partial<Record<K, V>> | string | undefined, argumentName: string, parse: (m: string) => Partial<Record<K, V>>,
427
- isValidKey: (k: unknown) => k is K, isValidValue: (v: unknown) => v is V): Partial<Record<K, V>> | undefined {
428
-
429
- if (markers === undefined || markers === null) {
430
- return undefined;
431
- }
432
- else if (typeof markers === 'string') {
433
- return parse(markers);
434
- }
435
- else if (typeof markers === 'object') {
436
- const result: Partial<Record<K, V>> = {};
437
- Object.entries(markers).forEach(([ key, value ]) => {
438
- if (isValidKey(key) && isValidValue(value)) {
439
- result[key] = value;
440
- }
441
- });
442
- return result;
443
- }
444
- else {
445
- throw new IllegalArgument('Chessboard', argumentName);
446
- }
483
+ isValidKey: (k: unknown) => k is K, isValidValue: (v: unknown) => v is V): Partial<Record<K, V>> | undefined {
484
+
485
+ if (markers === undefined || markers === null) {
486
+ return undefined;
487
+ }
488
+ else if (typeof markers === 'string') {
489
+ return parse(markers);
490
+ }
491
+ else if (typeof markers === 'object') {
492
+ const result: Partial<Record<K, V>> = {};
493
+ Object.entries(markers).forEach(([ key, value ]) => {
494
+ if (isValidKey(key) && isValidValue(value)) {
495
+ result[key] = value;
496
+ }
497
+ });
498
+ return result;
499
+ }
500
+ else {
501
+ throw new IllegalArgument('Chessboard', argumentName);
502
+ }
447
503
  }
448
504
 
449
505
 
450
506
  function isTextMarkerElement(value: unknown): value is { symbol: AnnotationSymbol, color: AnnotationColor } {
451
- if (typeof value !== 'object' || value === null) {
452
- return false;
453
- }
454
- return isAnnotationSymbol((value as Record<string, unknown>).symbol) && isAnnotationColor((value as Record<string, unknown>).color);
507
+ if (typeof value !== 'object' || value === null) {
508
+ return false;
509
+ }
510
+ return isAnnotationSymbol((value as Record<string, unknown>).symbol) && isAnnotationColor((value as Record<string, unknown>).color);
455
511
  }
456
512
 
457
513
 
@@ -459,23 +515,23 @@ function isTextMarkerElement(value: unknown): value is { symbol: AnnotationSymbo
459
515
  * Sanitization method for the small-screen limits parameter.
460
516
  */
461
517
  function sanitizeSmallScreenLimits(smallScreenLimits: SmallScreenLimit[] | undefined, exceptionBuilder: () => IllegalArgument): SmallScreenLimit[] {
462
- if (smallScreenLimits === undefined || smallScreenLimits === null) {
463
- return [];
464
- }
465
- else if (!Array.isArray(smallScreenLimits)) {
466
- throw exceptionBuilder();
467
- }
468
- else {
469
- return smallScreenLimits.map(smallScreenLimit => {
470
- if (typeof smallScreenLimit !== 'object' || smallScreenLimit === null) {
471
- throw exceptionBuilder();
472
- }
473
- return {
474
- width: sanitizeInteger(smallScreenLimit.width, exceptionBuilder),
475
- squareSize: sanitizeOptional(smallScreenLimit.squareSize, val => sanitizeBoundedInteger(val, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, exceptionBuilder)),
476
- coordinateVisible: sanitizeOptional(smallScreenLimit.coordinateVisible, sanitizeBoolean),
477
- turnVisible: sanitizeOptional(smallScreenLimit.turnVisible, sanitizeBoolean)
478
- };
479
- });
480
- }
518
+ if (smallScreenLimits === undefined || smallScreenLimits === null) {
519
+ return [];
520
+ }
521
+ else if (!Array.isArray(smallScreenLimits)) {
522
+ throw exceptionBuilder();
523
+ }
524
+ else {
525
+ return smallScreenLimits.map(smallScreenLimit => {
526
+ if (typeof smallScreenLimit !== 'object' || smallScreenLimit === null) {
527
+ throw exceptionBuilder();
528
+ }
529
+ return {
530
+ width: sanitizeInteger(smallScreenLimit.width, exceptionBuilder),
531
+ squareSize: sanitizeOptional(smallScreenLimit.squareSize, val => sanitizeBoundedInteger(val, MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, exceptionBuilder)),
532
+ coordinateVisible: sanitizeOptional(smallScreenLimit.coordinateVisible, sanitizeBoolean),
533
+ turnVisible: sanitizeOptional(smallScreenLimit.turnVisible, sanitizeBoolean)
534
+ };
535
+ });
536
+ }
481
537
  }