kokopu-react 3.0.2 → 3.1.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 (120) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/dist/lib/chessboard/BoardProperties.d.ts +91 -0
  3. package/dist/lib/chessboard/BoardProperties.js +43 -0
  4. package/dist/lib/chessboard/BoardProperties.js.map +1 -0
  5. package/dist/lib/chessboard/Chessboard.d.ts +14 -58
  6. package/dist/lib/chessboard/Chessboard.js +26 -92
  7. package/dist/lib/chessboard/Chessboard.js.map +1 -1
  8. package/dist/lib/errorbox/parsing.d.ts +57 -0
  9. package/dist/lib/errorbox/parsing.js +140 -0
  10. package/dist/lib/errorbox/parsing.js.map +1 -0
  11. package/dist/lib/i18n.d.ts +5 -0
  12. package/dist/lib/i18n.js +6 -0
  13. package/dist/lib/i18n.js.map +1 -1
  14. package/dist/lib/index.d.ts +4 -1
  15. package/dist/lib/index.js +2 -0
  16. package/dist/lib/index.js.map +1 -1
  17. package/dist/lib/movetext/Movetext.css +0 -13
  18. package/dist/lib/movetext/Movetext.d.ts +9 -43
  19. package/dist/lib/movetext/Movetext.js +15 -67
  20. package/dist/lib/movetext/Movetext.js.map +1 -1
  21. package/dist/lib/movetext/MovetextImpl.d.ts +7 -28
  22. package/dist/lib/movetext/MovetextImpl.js +15 -94
  23. package/dist/lib/movetext/MovetextImpl.js.map +1 -1
  24. package/dist/lib/navigationboard/NavigationBoard.d.ts +118 -0
  25. package/dist/lib/navigationboard/NavigationBoard.js +112 -0
  26. package/dist/lib/navigationboard/NavigationBoard.js.map +1 -0
  27. package/dist/lib/navigationboard/NavigationField.css +36 -0
  28. package/dist/lib/navigationboard/NavigationField.d.ts +81 -0
  29. package/dist/lib/navigationboard/NavigationField.js +154 -0
  30. package/dist/lib/navigationboard/NavigationField.js.map +1 -0
  31. package/dist/lib/navigationboard/NavigationToolbar.css +51 -0
  32. package/dist/lib/navigationboard/NavigationToolbar.d.ts +41 -0
  33. package/dist/lib/navigationboard/NavigationToolbar.js +124 -0
  34. package/dist/lib/navigationboard/NavigationToolbar.js.map +1 -0
  35. package/doc_src/demo/PageChessboardMove.tsx +17 -0
  36. package/doc_src/demo/PageNavigationBoardBase.tsx +194 -0
  37. package/doc_src/demo/PageNavigationBoardUncontrolledVsControlled.tsx +114 -0
  38. package/doc_src/demo/game-3.pgn +10 -0
  39. package/doc_src/examples/Movetext.md +1 -1
  40. package/doc_src/examples/NavigationBoard.md +15 -0
  41. package/package.json +16 -16
  42. package/scripts/doc.styleguide.config.js +12 -2
  43. package/scripts/docker-compose.yml +1 -1
  44. package/src/chessboard/BoardProperties.ts +131 -0
  45. package/src/chessboard/Chessboard.tsx +41 -170
  46. package/src/errorbox/parsing.tsx +158 -0
  47. package/src/i18n.ts +7 -0
  48. package/src/index.ts +5 -1
  49. package/src/movetext/Movetext.css +0 -13
  50. package/src/movetext/Movetext.tsx +20 -85
  51. package/src/movetext/MovetextImpl.tsx +28 -115
  52. package/src/navigationboard/NavigationBoard.tsx +231 -0
  53. package/src/navigationboard/NavigationField.css +36 -0
  54. package/src/navigationboard/NavigationField.tsx +185 -0
  55. package/src/navigationboard/NavigationToolbar.css +51 -0
  56. package/src/navigationboard/NavigationToolbar.tsx +174 -0
  57. package/test/05_chessboard_graphic.js +2 -0
  58. package/test/09_chessboard_small_screens.js +21 -0
  59. package/test/10_movetext_graphic.js +5 -0
  60. package/test/11_movetext_interaction.js +12 -8
  61. package/test/12_navigation_board.js +168 -0
  62. package/test/graphic_references/05_chessboard_graphic/move/illegal_null-move.png +0 -0
  63. package/test/graphic_references/05_chessboard_graphic/move/null-move.png +0 -0
  64. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_bottom_component.png +0 -0
  65. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/default_top_component.png +0 -0
  66. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_bottom_component.png +0 -0
  67. package/test/graphic_references/09_chessboard_small_screens/top_bottom_components/reduced_top_component.png +0 -0
  68. package/test/graphic_references/10_movetext_graphic/format_move/format_text.png +0 -0
  69. package/test/graphic_references/10_movetext_graphic/options/wrong_piece_symbol_value.png +0 -0
  70. package/test/graphic_references/11_movetext_interaction/select_moves/click_on_unselected_without_callback.png +0 -0
  71. package/test/graphic_references/12_navigation_board/controlled/default.png +0 -0
  72. package/test/graphic_references/12_navigation_board/controlled/forbidden_navigation_0.png +0 -0
  73. package/test/graphic_references/12_navigation_board/controlled/forbidden_navigation_1.png +0 -0
  74. package/test/graphic_references/12_navigation_board/controlled/invalid_node_id.png +0 -0
  75. package/test/graphic_references/12_navigation_board/controlled/no_callback_defined_0.png +0 -0
  76. package/test/graphic_references/12_navigation_board/controlled/no_callback_defined_1.png +0 -0
  77. package/test/graphic_references/12_navigation_board/controlled/overriding_initial_node_id.png +0 -0
  78. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_0.png +0 -0
  79. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_1.png +0 -0
  80. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_2.png +0 -0
  81. package/test/graphic_references/12_navigation_board/controlled/regular_navigation_3.png +0 -0
  82. package/test/graphic_references/12_navigation_board/error/pgn_parsing_error.png +0 -0
  83. package/test/graphic_references/12_navigation_board/error/wrong_game_type.png +0 -0
  84. package/test/graphic_references/12_navigation_board/theme/custom_1.png +0 -0
  85. package/test/graphic_references/12_navigation_board/theme/custom_2.png +0 -0
  86. package/test/graphic_references/12_navigation_board/theme/large.png +0 -0
  87. package/test/graphic_references/12_navigation_board/theme/small.png +0 -0
  88. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_0.png +0 -0
  89. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_1.png +0 -0
  90. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_beginning_2.png +0 -0
  91. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_0.png +0 -0
  92. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_1.png +0 -0
  93. package/test/graphic_references/12_navigation_board/uncontrolled/forbidden_navigation_at_end_2.png +0 -0
  94. package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_at_end.png +0 -0
  95. package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_at_specific_id.png +0 -0
  96. package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_default.png +0 -0
  97. package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_default_with_game_index.png +0 -0
  98. package/test/graphic_references/12_navigation_board/uncontrolled/initial_state_on_null-move.png +0 -0
  99. package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_0.png +0 -0
  100. package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_1.png +0 -0
  101. package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_2.png +0 -0
  102. package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_3.png +0 -0
  103. package/test/graphic_references/12_navigation_board/uncontrolled/navigation_with_keys_4.png +0 -0
  104. package/test/graphic_references/12_navigation_board/uncontrolled/regular_flipping_0.png +0 -0
  105. package/test/graphic_references/12_navigation_board/uncontrolled/regular_flipping_1.png +0 -0
  106. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_0.png +0 -0
  107. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_1.png +0 -0
  108. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_2.png +0 -0
  109. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_3.png +0 -0
  110. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_4.png +0 -0
  111. package/test/graphic_references/12_navigation_board/uncontrolled/regular_navigation_5.png +0 -0
  112. package/test/graphic_test_app/05_chessboard_graphic/move.tsx +2 -0
  113. package/test/graphic_test_app/09_chessboard_small_screens/top_bottom_components.tsx +48 -0
  114. package/test/graphic_test_app/10_movetext_graphic/format_move.tsx +31 -0
  115. package/test/graphic_test_app/10_movetext_graphic/options.tsx +1 -0
  116. package/test/graphic_test_app/11_movetext_interaction/select_moves.tsx +1 -0
  117. package/test/graphic_test_app/12_navigation_board/controlled.tsx +43 -0
  118. package/test/graphic_test_app/12_navigation_board/error.tsx +34 -0
  119. package/test/graphic_test_app/12_navigation_board/theme.tsx +38 -0
  120. package/test/graphic_test_app/12_navigation_board/uncontrolled.tsx +56 -0
@@ -24,32 +24,21 @@
24
24
 
25
25
  import * as React from 'react';
26
26
 
27
- import { exception as kokopuException, MoveDescriptor, Position, Square, GameVariant, isSquare, isSquareCouple, isGameVariant } from 'kokopu';
27
+ import { MoveDescriptor, Position, Square, isSquare, isSquareCouple } from 'kokopu';
28
28
 
29
29
  import { IllegalArgument } from '../exception';
30
- import { i18n } from '../i18n';
31
30
  import { sanitizeString, sanitizeBoolean, sanitizePartialObject, sanitizeInteger, sanitizeBoundedInteger, sanitizeOptional } from '../sanitization';
32
31
  import { MIN_SQUARE_SIZE, MAX_SQUARE_SIZE, Colorset, Pieceset, AnnotationColor, AnnotationSymbol, SquareMarkerSet, TextMarkerSet, ArrowMarkerSet,
33
32
  isAnnotationColor, isAnnotationSymbol, parseSquareMarkers, parseTextMarkers, parseArrowMarkers } from '../types';
34
33
 
35
- import { colorsets, DEFAULT_COLORSET } from './colorsets';
36
- import { piecesets, DEFAULT_PIECESET } from './piecesets';
34
+ import { StaticBoardGraphicProps, DynamicBoardGraphicProps, SmallScreenLimit, DEFAULT_SQUARE_SIZE, defaultDynamicBoardProps } from './BoardProperties';
35
+ import { colorsets } from './colorsets';
36
+ import { piecesets } from './piecesets';
37
37
  import { ChessboardImpl, chessboardSize } from './ChessboardImpl';
38
- import { ErrorBox } from '../errorbox/ErrorBox';
38
+ import { parsePosition, parseMove } from '../errorbox/parsing';
39
39
 
40
40
 
41
- /**
42
- * Define a limit applicable to the parameters of a {@link Chessboard} on small-screen devices.
43
- */
44
- export interface SmallScreenLimit {
45
- width: number;
46
- squareSize?: number;
47
- coordinateVisible?: boolean;
48
- turnVisible?: boolean;
49
- }
50
-
51
-
52
- export interface ChessboardProps {
41
+ export interface ChessboardProps extends DynamicBoardGraphicProps {
53
42
 
54
43
  /**
55
44
  * Displayed position. Can be a [kokopu.Position](https://kokopu.yo35.org/docs/current/classes/Position.html) object,
@@ -64,8 +53,8 @@ export interface ChessboardProps {
64
53
 
65
54
  /**
66
55
  * Displayed move (optional), defined either as a [kokopu.MoveDescriptor](https://kokopu.yo35.org/docs/current/classes/MoveDescriptor.html) object
67
- * or as a [SAN string](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)) (e.g. `'Nf3'`). In both cases, it must represent
68
- * a legal move in position defined in attribute `position`.
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`.
69
58
  */
70
59
  move?: MoveDescriptor | string;
71
60
 
@@ -91,61 +80,6 @@ export interface ChessboardProps {
91
80
  */
92
81
  flipped: boolean;
93
82
 
94
- /**
95
- * Size of the squares (in pixels). Must be an integer between `Chessboard.minSquareSize()` and `Chessboard.maxSquareSize()`.
96
- */
97
- squareSize: number;
98
-
99
- /**
100
- * Whether the row and column coordinates are visible or not.
101
- */
102
- coordinateVisible: boolean;
103
-
104
- /**
105
- * Whether the turn flag is visible or not.
106
- */
107
- turnVisible: boolean;
108
-
109
- /**
110
- * Whether moves are highlighted with an arrow or not.
111
- */
112
- moveArrowVisible: boolean;
113
-
114
- /**
115
- * Color of the move arrow.
116
- */
117
- moveArrowColor: AnnotationColor;
118
-
119
- /**
120
- * Whether moves are animated or not.
121
- */
122
- animated: boolean;
123
-
124
- /**
125
- * Color theme ID. Must be a property of `Chessboard.colorsets()`.
126
- */
127
- colorset: string;
128
-
129
- /**
130
- * Piece theme ID. Must be a property of `Chessboard.piecesets()`.
131
- */
132
- pieceset: string;
133
-
134
- /**
135
- * Limits applicable on small-screen devices. For instance, if set to
136
- * ```
137
- * [
138
- * { width: 768, squareSize: 40 },
139
- * { width: 375, squareSize: 24, coordinateVisible: false }
140
- * ]
141
- * ```
142
- * then on screens with resolution ≤768 pixels, the square size will be limited to 40 (whatever the value
143
- * of the `squareSize` attribute); in addition, on screens with resolution ≤375 pixels, the square size
144
- * will be limited to 24 and the row and column coordinates will always be hidden (whatever the value of the
145
- * `coordinateVisible` attribute).
146
- */
147
- smallScreenLimits?: SmallScreenLimit[];
148
-
149
83
  /**
150
84
  * Type of action allowed with the mouse on the chessboard. If undefined, then the user cannot interact with the component.
151
85
  *
@@ -180,6 +114,18 @@ export interface ChessboardProps {
180
114
  * Callback invoked when a move is played (only if `interactionMode` is set to `'playMoves'`).
181
115
  */
182
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;
183
129
  }
184
130
 
185
131
 
@@ -188,9 +134,6 @@ interface ChessboardState {
188
134
  }
189
135
 
190
136
 
191
- const DEFAULT_SQUARE_SIZE = 40;
192
-
193
-
194
137
  /**
195
138
  * SVG image representing a chessboard diagram. Optionally, the user may interact with the board (move pieces, click on squares...).
196
139
  * Annotations such as square markers or arrows can also be added to the board.
@@ -198,16 +141,9 @@ const DEFAULT_SQUARE_SIZE = 40;
198
141
  export class Chessboard extends React.Component<ChessboardProps, ChessboardState> {
199
142
 
200
143
  static defaultProps: Partial<ChessboardProps> = {
144
+ ...defaultDynamicBoardProps(),
201
145
  position: 'start',
202
146
  flipped: false,
203
- squareSize: DEFAULT_SQUARE_SIZE,
204
- coordinateVisible: true,
205
- turnVisible: true,
206
- moveArrowVisible: true,
207
- moveArrowColor: 'b',
208
- animated: false,
209
- colorset: DEFAULT_COLORSET,
210
- pieceset: DEFAULT_PIECESET,
211
147
  };
212
148
 
213
149
  private windowResizeListener = () => this.handleWindowResize();
@@ -234,16 +170,20 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
234
170
  render() {
235
171
 
236
172
  // Validate the position and move attributes.
237
- const positionInfo = parsePosition(this.props.position);
173
+ const positionInfo = parsePosition(this.props.position, 'Chessboard');
238
174
  if (positionInfo.error) {
239
- return <ErrorBox title={i18n.INVALID_FEN_ERROR_TITLE} message={positionInfo.message} />;
175
+ return positionInfo.errorBox;
240
176
  }
241
- const position = positionInfo.position;
242
- const moveInfo = parseMove(position, this.props.move);
177
+ let position = positionInfo.position;
178
+ const moveInfo = parseMove(position, this.props.move, 'Chessboard');
243
179
  if (moveInfo.error) {
244
- return <ErrorBox title={i18n.INVALID_NOTATION_ERROR_TITLE} message={moveInfo.message} />;
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();
245
186
  }
246
- const move = moveInfo.move;
247
187
 
248
188
  // Validate the markers
249
189
  const sqm = parseMarkers(this.props.squareMarkers, 'squareMarkers', parseSquareMarkers, isSquare, isAnnotationColor);
@@ -274,6 +214,11 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
274
214
  const actualSquareSize = computeSquareSizeForSmallScreens(squareSize, smallScreenLimits, this.state.windowWidth);
275
215
  const actualCoordinateVisible = computeCoordinateVisibleForSmallScreens(coordinateVisible, smallScreenLimits, this.state.windowWidth);
276
216
  const actualTurnVisible = computeTurnVisibleForSmallScreens(turnVisible, smallScreenLimits, this.state.windowWidth);
217
+ const auxilliaryComponentAttr = {
218
+ squareSize: actualSquareSize,
219
+ coordinateVisible: actualCoordinateVisible,
220
+ turnVisible: actualTurnVisible,
221
+ };
277
222
 
278
223
  // Validate the interaction attributes and the callbacks.
279
224
  const interactionMode = this.getInteractionModeAndValidateEditedArrowColor();
@@ -283,7 +228,8 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
283
228
  // (mandatory to ensure that the move animation works properly and that the internal state of the component remains consistent).
284
229
  const key = `${position.variant()}|${position.fen()}|${move ? position.notation(move) : ''}`;
285
230
 
286
- return (
231
+ return (<>
232
+ {this.props.topComponent ? this.props.topComponent(auxilliaryComponentAttr) : undefined}
287
233
  <ChessboardImpl key={key}
288
234
  position={position} move={move} squareMarkers={sqm} textMarkers={txtm} arrowMarkers={arm} flipped={flipped}
289
235
  squareSize={actualSquareSize} coordinateVisible={actualCoordinateVisible} turnVisible={actualTurnVisible}
@@ -292,7 +238,8 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
292
238
  interactionMode={interactionMode} editedArrowColor={this.props.editedArrowColor}
293
239
  onPieceMoved={onPieceMoved} onSquareClicked={onSquareClicked} onArrowEdited={onArrowEdited} onMovePlayed={onMovePlayed}
294
240
  />
295
- );
241
+ {this.props.bottomComponent ? this.props.bottomComponent(auxilliaryComponentAttr) : undefined}
242
+ </>);
296
243
  }
297
244
 
298
245
  private getInteractionModeAndValidateEditedArrowColor() {
@@ -339,7 +286,7 @@ export class Chessboard extends React.Component<ChessboardProps, ChessboardState
339
286
  * Return the maximum square size that would allow the chessboard to fit in a rectangle of size `width x height`.
340
287
  */
341
288
  static adaptSquareSize(width: number, height: number, attr?: Partial<Pick<ChessboardProps, 'coordinateVisible' | 'turnVisible' | 'smallScreenLimits'>>): number {
342
- let { coordinateVisible, turnVisible, smallScreenLimits } = sanitizePartialObject(attr, () => new IllegalArgument('Chessboard.size()', 'attr'));
289
+ let { coordinateVisible, turnVisible, smallScreenLimits } = sanitizePartialObject(attr, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'attr'));
343
290
 
344
291
  // Sanitize the arguments.
345
292
  width = sanitizeInteger(width, () => new IllegalArgument('Chessboard.adaptSquareSize()', 'width'));
@@ -473,82 +420,6 @@ function computeSmallScreenLimits<T>(smallScreenLimits: SmallScreenLimit[], wind
473
420
  }
474
421
 
475
422
 
476
- /**
477
- * Try to interpret the given object as a chess position.
478
- */
479
- function parsePosition(position: Position | string): { error: true, message: string } | { error: false, position: Position } {
480
- if (position instanceof Position) {
481
- return { error: false, position: position };
482
- }
483
- else if (position === 'start' || position === 'empty') {
484
- return { error: false, position: new Position(position) };
485
- }
486
- else if (typeof position === 'string') {
487
- try {
488
- const { variant, fen } = splitGameVariantAndFEN(position);
489
- const result = new Position(variant, 'empty');
490
- result.fen(fen);
491
- return { error: false, position: result };
492
- }
493
- catch (e) {
494
- // istanbul ignore else
495
- if (e instanceof kokopuException.InvalidFEN) {
496
- return { error: true, message: e.message };
497
- }
498
- else {
499
- throw e;
500
- }
501
- }
502
- }
503
- else {
504
- throw new IllegalArgument('Chessboard', 'position');
505
- }
506
- }
507
-
508
-
509
- /**
510
- * Look for an optional 'chess-variant:' prefix in the position attribute.
511
- */
512
- function splitGameVariantAndFEN(position: string): { variant: GameVariant, fen: string } {
513
- const separatorIndex = position.indexOf(':');
514
- if (separatorIndex < 0) {
515
- return { variant: 'regular', fen: position };
516
- }
517
- const variant = position.substring(0, separatorIndex);
518
- return isGameVariant(variant) ? { variant: variant, fen: position.substring(separatorIndex + 1) } : { variant: 'regular', fen: position };
519
- }
520
-
521
-
522
- /**
523
- * Try to interpret the given object `move` as a move descriptor based on the given position.
524
- */
525
- function parseMove(position: Position, move: MoveDescriptor | string | undefined): { error: true, message: string } | { error: false, move: MoveDescriptor | undefined } {
526
- if (move === undefined || move === null) {
527
- return { error: false, move: undefined };
528
- }
529
- else if (move instanceof MoveDescriptor) {
530
- return { error: false, move: move };
531
- }
532
- else if (typeof move === 'string') {
533
- try {
534
- return { error: false, move: position.notation(move) };
535
- }
536
- catch (e) {
537
- // istanbul ignore else
538
- if (e instanceof kokopuException.InvalidNotation) {
539
- return { error: true, message: e.message };
540
- }
541
- else {
542
- throw e;
543
- }
544
- }
545
- }
546
- else {
547
- throw new IllegalArgument('Chessboard', 'move');
548
- }
549
- }
550
-
551
-
552
423
  /**
553
424
  * Try to interpret the given object as a list of markers.
554
425
  */
@@ -0,0 +1,158 @@
1
+ /*!
2
+ * -------------------------------------------------------------------------- *
3
+ * *
4
+ * Kokopu-React - A React-based library of chess-related components. *
5
+ * <https://www.npmjs.com/package/kokopu-react> *
6
+ * Copyright (C) 2021-2024 Yoann Le Montagner <yo35 -at- melix.net> *
7
+ * *
8
+ * Kokopu-React is free software: you can redistribute it and/or *
9
+ * modify it under the terms of the GNU Lesser General Public License *
10
+ * as published by the Free Software Foundation, either version 3 of *
11
+ * the License, or (at your option) any later version. *
12
+ * *
13
+ * Kokopu-React is distributed in the hope that it will be useful, *
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16
+ * GNU Lesser General Public License for more details. *
17
+ * *
18
+ * You should have received a copy of the GNU Lesser General *
19
+ * Public License along with this program. If not, see *
20
+ * <http://www.gnu.org/licenses/>. *
21
+ * *
22
+ * -------------------------------------------------------------------------- */
23
+
24
+
25
+ import * as React from 'react';
26
+
27
+ import { exception as kokopuException, i18n as kokopuI18n, Database, Game, GameVariant, MoveDescriptor, Position, isGameVariant, pgnRead } from 'kokopu';
28
+
29
+ import { IllegalArgument } from '../exception';
30
+ import { i18n } from '../i18n';
31
+
32
+ import { ErrorBox } from './ErrorBox';
33
+
34
+
35
+ /**
36
+ * Try to interpret the given object as a chess position.
37
+ */
38
+ export function parsePosition(position: Position | string, componentName: string):
39
+ { error: true, errorBox: JSX.Element } | { error: false, position: Position }
40
+ {
41
+ if (position instanceof Position) {
42
+ return { error: false, position: position };
43
+ }
44
+ else if (position === 'start' || position === 'empty') {
45
+ return { error: false, position: new Position(position) };
46
+ }
47
+ else if (typeof position === 'string') {
48
+ try {
49
+ const { variant, fen } = splitGameVariantAndFEN(position);
50
+ const result = new Position(variant, 'empty');
51
+ result.fen(fen);
52
+ return { error: false, position: result };
53
+ }
54
+ catch (e) {
55
+ // istanbul ignore else
56
+ if (e instanceof kokopuException.InvalidFEN) {
57
+ return { error: true, errorBox: <ErrorBox title={i18n.INVALID_FEN_ERROR_TITLE} message={e.message} /> };
58
+ }
59
+ else {
60
+ throw e;
61
+ }
62
+ }
63
+ }
64
+ else {
65
+ throw new IllegalArgument(componentName, 'position');
66
+ }
67
+ }
68
+
69
+
70
+ /**
71
+ * Look for an optional 'chess-variant:' prefix in the position attribute.
72
+ */
73
+ function splitGameVariantAndFEN(position: string): { variant: GameVariant, fen: string } {
74
+ const separatorIndex = position.indexOf(':');
75
+ if (separatorIndex < 0) {
76
+ return { variant: 'regular', fen: position };
77
+ }
78
+ const variant = position.substring(0, separatorIndex);
79
+ return isGameVariant(variant) ? { variant: variant, fen: position.substring(separatorIndex + 1) } : { variant: 'regular', fen: position };
80
+ }
81
+
82
+
83
+ /**
84
+ * Try to interpret the given object `move` as a move descriptor based on the given position.
85
+ */
86
+ export function parseMove(position: Position, move: MoveDescriptor | string | undefined, componentName: string):
87
+ { error: true, errorBox: JSX.Element } | { error: false, type: 'none' | 'null-move' } | { error: false, type: 'regular', move: MoveDescriptor }
88
+ {
89
+ if (move === undefined || move === null) {
90
+ return { error: false, type: 'none' };
91
+ }
92
+ else if (move instanceof MoveDescriptor) {
93
+ return { error: false, type: 'regular', move: move };
94
+ }
95
+ else if (typeof move === 'string') {
96
+ if (move === '--') {
97
+ if (position.isNullMoveLegal()) {
98
+ return { error: false, type: 'null-move' };
99
+ }
100
+ else {
101
+ return { error: true, errorBox: <ErrorBox title={i18n.INVALID_NOTATION_ERROR_TITLE} message={kokopuI18n.ILLEGAL_NULL_MOVE} /> };
102
+ }
103
+ }
104
+ else {
105
+ try {
106
+ return { error: false, type: 'regular', move: position.notation(move) };
107
+ }
108
+ catch (e) {
109
+ // istanbul ignore else
110
+ if (e instanceof kokopuException.InvalidNotation) {
111
+ return { error: true, errorBox: <ErrorBox title={i18n.INVALID_NOTATION_ERROR_TITLE} message={e.message} /> };
112
+ }
113
+ else {
114
+ throw e;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ else {
120
+ throw new IllegalArgument(componentName, 'move');
121
+ }
122
+ }
123
+
124
+
125
+ /**
126
+ * Try to interpret the given object as a chess game.
127
+ */
128
+ export function parseGame(game: Game | Database | string, gameIndex: number, componentName: string):
129
+ { error: true, errorBox: JSX.Element } | { error: false, game: Game }
130
+ {
131
+ if (game instanceof Game) {
132
+ return { error: false, game: game };
133
+ }
134
+ else if (game instanceof Database || typeof game === 'string') {
135
+ if (!Number.isInteger(gameIndex) || gameIndex < 0) {
136
+ throw new IllegalArgument(componentName, 'gameIndex');
137
+ }
138
+ try {
139
+ const result = game instanceof Database ? game.game(gameIndex) : pgnRead(game, gameIndex);
140
+ return { error: false, game: result };
141
+ }
142
+ catch (e) {
143
+ // istanbul ignore else
144
+ if (e instanceof kokopuException.InvalidPGN) {
145
+ return {
146
+ error: true,
147
+ errorBox: <ErrorBox title={i18n.INVALID_PGN_ERROR_TITLE} message={e.message} text={e.pgn} errorIndex={e.index} lineNumber={e.lineNumber} />,
148
+ };
149
+ }
150
+ else {
151
+ throw e;
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ throw new IllegalArgument(componentName, 'game');
157
+ }
158
+ }
package/src/i18n.ts CHANGED
@@ -37,6 +37,13 @@ export let LINE = 'line {0}';
37
37
  export let INVALID_FEN_ERROR_TITLE = 'Invalid FEN string.';
38
38
  export let INVALID_NOTATION_ERROR_TITLE = 'Invalid move notation.';
39
39
 
40
+ // Navigation board
41
+ export let TOOLTIP_GO_FIRST = 'Go to the beginning of the game';
42
+ export let TOOLTIP_GO_PREVIOUS = 'Go to the previous move';
43
+ export let TOOLTIP_GO_NEXT = 'Go to the next move';
44
+ export let TOOLTIP_GO_LAST = 'Go to the end of the game';
45
+ export let TOOLTIP_FLIP = 'Flip the board';
46
+
40
47
  // Movetext
41
48
  export let PIECE_SYMBOLS = { 'K':'K', 'Q':'Q', 'R':'R', 'B':'B', 'N':'N', 'P':'P' };
42
49
  export let ANNOTATED_BY = 'Annotated by {0}';
package/src/index.ts CHANGED
@@ -36,7 +36,11 @@ export { TextMarkerIcon, TextMarkerIconProps } from './icons/TextMarkerIcon';
36
36
  export { ArrowMarkerIcon, ArrowMarkerIconProps } from './icons/ArrowMarkerIcon';
37
37
  export { ChessPieceIcon, ChessPieceIconProps } from './icons/ChessPieceIcon';
38
38
 
39
- export { Chessboard, ChessboardProps, SmallScreenLimit } from './chessboard/Chessboard';
39
+ export { SmallScreenLimit } from './chessboard/BoardProperties';
40
+ export { Chessboard, ChessboardProps } from './chessboard/Chessboard';
40
41
 
41
42
  export { formatMove, moveFormatter } from './movetext/moveFormatter';
42
43
  export { Movetext, MovetextProps, MoveSelectEventOrigin } from './movetext/Movetext';
44
+
45
+ export { firstNodeId, previousNodeId, nextNodeId, lastNodeId } from './navigationboard/NavigationField';
46
+ export { NavigationBoard, NavigationBoardProps } from './navigationboard/NavigationBoard';
@@ -108,16 +108,3 @@ div.kokopu-subVariation {
108
108
  text-decoration: underline;
109
109
  cursor: pointer;
110
110
  }
111
-
112
- .kokopu-focusFieldContainer {
113
- position: fixed;
114
- bottom: 0px;
115
- right: 0px;
116
- width: 0px;
117
- height: 0px;
118
- overflow: hidden;
119
- }
120
-
121
- .kokopu-focusField {
122
- position: absolute; /* see #1 */
123
- }