@alepot55/chessboardjs 2.2.1 → 2.3.3

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 (81) hide show
  1. package/.eslintrc.json +227 -0
  2. package/.github/instructions/copilot-instuctions.md +1671 -0
  3. package/README.md +125 -401
  4. package/assets/themes/alepot/theme.json +42 -0
  5. package/assets/themes/default/theme.json +42 -0
  6. package/chessboard.bundle.js +708 -58
  7. package/config/jest.config.js +15 -0
  8. package/config/rollup.config.js +35 -0
  9. package/dist/chessboard.cjs.js +10515 -0
  10. package/dist/chessboard.css +197 -0
  11. package/dist/chessboard.esm.js +10446 -0
  12. package/dist/chessboard.iife.js +10520 -0
  13. package/dist/chessboard.umd.js +10521 -0
  14. package/jest.config.js +2 -7
  15. package/package.json +18 -3
  16. package/rollup.config.js +2 -11
  17. package/{chessboard.move.js → src/components/Move.js} +3 -3
  18. package/src/components/Piece.js +273 -0
  19. package/{chessboard.square.js → src/components/Square.js} +60 -7
  20. package/src/constants/index.js +15 -0
  21. package/src/constants/positions.js +62 -0
  22. package/src/core/Chessboard.js +1969 -0
  23. package/src/core/ChessboardConfig.js +458 -0
  24. package/src/core/ChessboardFactory.js +385 -0
  25. package/src/core/index.js +141 -0
  26. package/src/errors/ChessboardError.js +133 -0
  27. package/src/errors/index.js +15 -0
  28. package/src/errors/messages.js +189 -0
  29. package/src/index.js +103 -0
  30. package/src/services/AnimationService.js +180 -0
  31. package/src/services/BoardService.js +156 -0
  32. package/src/services/CoordinateService.js +355 -0
  33. package/src/services/EventService.js +807 -0
  34. package/src/services/MoveService.js +594 -0
  35. package/src/services/PieceService.js +303 -0
  36. package/src/services/PositionService.js +237 -0
  37. package/src/services/ValidationService.js +673 -0
  38. package/src/services/index.js +14 -0
  39. package/src/styles/animations.css +46 -0
  40. package/{chessboard.css → src/styles/board.css} +3 -0
  41. package/src/styles/index.css +4 -0
  42. package/src/styles/pieces.css +66 -0
  43. package/src/utils/animations.js +37 -0
  44. package/{chess.js → src/utils/chess.js} +16 -16
  45. package/src/utils/coordinates.js +62 -0
  46. package/src/utils/cross-browser.js +150 -0
  47. package/src/utils/logger.js +422 -0
  48. package/src/utils/performance.js +311 -0
  49. package/src/utils/validation.js +458 -0
  50. package/tests/unit/chessboard-config-animations.test.js +106 -0
  51. package/tests/unit/chessboard-robust.test.js +163 -0
  52. package/tests/unit/chessboard.test.js +183 -0
  53. package/chessboard.config.js +0 -147
  54. package/chessboard.js +0 -979
  55. package/chessboard.piece.js +0 -115
  56. package/test/chessboard.test.js +0 -128
  57. /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
  58. /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
  59. /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
  60. /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
  61. /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
  62. /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
  63. /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
  64. /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
  65. /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
  66. /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
  67. /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
  68. /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
  69. /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
  70. /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
  71. /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
  72. /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
  73. /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
  74. /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
  75. /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
  76. /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
  77. /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
  78. /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
  79. /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
  80. /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
  81. /package/{.babelrc → config/.babelrc} +0 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Service for managing chess pieces and their operations
3
+ * @module services/PieceService
4
+ * @since 2.0.0
5
+ */
6
+
7
+ import Piece from '../components/Piece.js';
8
+ import { PieceError, ValidationError } from '../errors/ChessboardError.js';
9
+ import { ERROR_MESSAGES } from '../errors/messages.js';
10
+ import { PIECE_TYPES, PIECE_COLORS } from '../constants/positions.js';
11
+
12
+ /**
13
+ * Service responsible for piece management and operations
14
+ * @class
15
+ */
16
+ export class PieceService {
17
+ /**
18
+ * Creates a new PieceService instance
19
+ * @param {ChessboardConfig} config - Board configuration
20
+ */
21
+ constructor(config) {
22
+ this.config = config;
23
+ }
24
+
25
+ /**
26
+ * Gets the path to a piece asset
27
+ * @param {string} piece - Piece identifier (e.g., 'wK', 'bP')
28
+ * @returns {string} Path to piece asset
29
+ * @throws {ValidationError} When piecesPath configuration is invalid
30
+ */
31
+ getPiecePath(piece) {
32
+ const { piecesPath } = this.config;
33
+
34
+ if (typeof piecesPath === 'string') {
35
+ return `${piecesPath}/${piece}.svg`;
36
+ } else if (typeof piecesPath === 'object' && piecesPath !== null) {
37
+ return piecesPath[piece];
38
+ } else if (typeof piecesPath === 'function') {
39
+ return piecesPath(piece);
40
+ } else {
41
+ throw new ValidationError(ERROR_MESSAGES.invalid_piecesPath, 'piecesPath', piecesPath);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Converts various piece formats to a Piece instance
47
+ * @param {string|Piece} piece - Piece in various formats
48
+ * @returns {Piece} Piece instance
49
+ * @throws {PieceError} When piece format is invalid
50
+ */
51
+ convertPiece(piece) {
52
+ if (piece instanceof Piece) {
53
+ return piece;
54
+ }
55
+
56
+ if (typeof piece === 'string' && piece.length === 2) {
57
+ const [first, second] = piece.split('');
58
+ let type, color;
59
+
60
+ // Check format: [type][color] (e.g., 'pw')
61
+ if (PIECE_TYPES.includes(first.toLowerCase()) && PIECE_COLORS.includes(second)) {
62
+ type = first.toLowerCase();
63
+ color = second;
64
+ }
65
+ // Check format: [color][type] (e.g., 'wP')
66
+ else if (PIECE_COLORS.includes(first) && PIECE_TYPES.includes(second.toLowerCase())) {
67
+ color = first;
68
+ type = second.toLowerCase();
69
+ } else {
70
+ throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
71
+ }
72
+
73
+ const piecePath = this.getPiecePath(type + color);
74
+ return new Piece(color, type, piecePath);
75
+ }
76
+
77
+ throw new PieceError(ERROR_MESSAGES.invalid_piece + piece, piece);
78
+ }
79
+
80
+ /**
81
+ * Adds a piece to a square with optional fade-in animation
82
+ * @param {Square} square - Target square
83
+ * @param {Piece} piece - Piece to add
84
+ * @param {boolean} [fade=true] - Whether to fade in the piece
85
+ * @param {Function} dragFunction - Function to handle drag events
86
+ * @param {Function} [callback] - Callback when animation completes
87
+ */
88
+ addPieceOnSquare(square, piece, fade = true, dragFunction, callback) {
89
+ console.debug(`[PieceService] addPieceOnSquare: ${piece.id} to ${square.id}`);
90
+ square.putPiece(piece);
91
+
92
+ if (dragFunction) {
93
+ piece.setDrag(dragFunction(square, piece));
94
+ }
95
+
96
+ if (fade && this.config.fadeTime > 0) {
97
+ piece.fadeIn(
98
+ this.config.fadeTime,
99
+ this.config.fadeAnimation,
100
+ this._getTransitionTimingFunction(),
101
+ callback
102
+ );
103
+ } else {
104
+ if (callback) callback();
105
+ }
106
+
107
+ piece.visible();
108
+ }
109
+
110
+ /**
111
+ * Removes a piece from a square with optional fade-out animation
112
+ * @param {Square} square - Source square
113
+ * @param {boolean} [fade=true] - Whether to fade out the piece
114
+ * @param {Function} [callback] - Callback when animation completes
115
+ * @returns {Piece} The removed piece
116
+ * @throws {PieceError} When square has no piece to remove
117
+ */
118
+ removePieceFromSquare(square, fade = true, callback) {
119
+ console.debug(`[PieceService] removePieceFromSquare: ${square.id}`);
120
+ square.check();
121
+
122
+ const piece = square.piece;
123
+ if (!piece) {
124
+ if (callback) callback();
125
+ throw new PieceError(ERROR_MESSAGES.square_no_piece, null, square.getId());
126
+ }
127
+
128
+ if (fade && this.config.fadeTime > 0) {
129
+ piece.fadeOut(
130
+ this.config.fadeTime,
131
+ this.config.fadeAnimation,
132
+ this._getTransitionTimingFunction(),
133
+ callback
134
+ );
135
+ } else {
136
+ if (callback) callback();
137
+ }
138
+
139
+ square.removePiece();
140
+ return piece;
141
+ }
142
+
143
+ /**
144
+ * Moves a piece to a new position with animation
145
+ * @param {Piece} piece - Piece to move
146
+ * @param {Square} targetSquare - Target square
147
+ * @param {number} duration - Animation duration
148
+ * @param {Function} [callback] - Callback function when animation completes
149
+ */
150
+ movePiece(piece, targetSquare, duration, callback) {
151
+ console.debug(`[PieceService] movePiece: ${piece.id} to ${targetSquare.id}`);
152
+ if (!piece) {
153
+ console.warn('PieceService.movePiece: piece is null, skipping animation');
154
+ if (callback) callback();
155
+ return;
156
+ }
157
+
158
+ piece.translate(
159
+ targetSquare,
160
+ duration,
161
+ this._getTransitionTimingFunction(),
162
+ this.config.moveAnimation,
163
+ callback
164
+ );
165
+ }
166
+
167
+ /**
168
+ * Handles piece translation with optional capture
169
+ * @param {Move} move - Move object containing from/to squares and piece
170
+ * @param {boolean} removeTarget - Whether to remove piece from target square
171
+ * @param {boolean} animate - Whether to animate the move
172
+ * @param {Function} [dragFunction] - Function to create drag handlers
173
+ * @param {Function} [callback] - Callback function when complete
174
+ */
175
+ translatePiece(move, removeTarget, animate, dragFunction = null, callback = null) {
176
+ console.debug(`[PieceService] translatePiece: ${move.piece.id} from ${move.from.id} to ${move.to.id}`);
177
+ if (!move.piece) {
178
+ console.warn('PieceService.translatePiece: move.piece is null, skipping translation');
179
+ if (callback) callback();
180
+ return;
181
+ }
182
+
183
+ if (removeTarget) {
184
+ // Deselect the captured piece before removing it
185
+ move.to.deselect();
186
+ this.removePieceFromSquare(move.to, false);
187
+ }
188
+
189
+ const changeSquareCallback = () => {
190
+ // Check if piece still exists and is on the source square
191
+ if (move.from.piece === move.piece) {
192
+ move.from.removePiece(true); // Preserve the piece when moving
193
+ }
194
+
195
+ // Only put piece if destination square doesn't already have it
196
+ if (move.to.piece !== move.piece) {
197
+ move.to.putPiece(move.piece);
198
+
199
+ // Re-attach drag handler if provided
200
+ if (dragFunction && this.config.draggable && move.piece.element) {
201
+ move.piece.setDrag(dragFunction(move.to, move.piece));
202
+ }
203
+ }
204
+
205
+ if (callback) callback();
206
+ };
207
+
208
+ // Check if piece is currently being dragged
209
+ const isDragging = move.piece.element.classList.contains('dragging');
210
+
211
+ if (isDragging) {
212
+ // If piece is being dragged, don't animate - just move it immediately
213
+ // The piece is already visually in the correct position from the drag
214
+ changeSquareCallback();
215
+ } else {
216
+ // Normal animation
217
+ const duration = animate ? this.config.moveTime : 0;
218
+ this.movePiece(move.piece, move.to, duration, changeSquareCallback);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Snaps a piece back to its original position
224
+ * @param {Square} square - Square containing the piece
225
+ * @param {boolean} [animate=true] - Whether to animate the snapback
226
+ */
227
+ snapbackPiece(square, animate = true) {
228
+ if (!square || !square.piece) {
229
+ return;
230
+ }
231
+ const piece = square.piece;
232
+ const duration = animate ? this.config.snapbackTime : 0;
233
+ console.debug(`[PieceService] snapbackPiece: ${piece.id} on ${square.id}`);
234
+ piece.translate(
235
+ square,
236
+ duration,
237
+ this._getTransitionTimingFunction(),
238
+ this.config.snapbackAnimation
239
+ );
240
+ }
241
+
242
+ /**
243
+ * Centers a piece in its square with animation (after successful drop)
244
+ * @param {Square} square - Square containing the piece to center
245
+ * @param {boolean} animate - Whether to animate the centering
246
+ */
247
+ centerPiece(square, animate = true) {
248
+ if (!square || !square.piece) {
249
+ return;
250
+ }
251
+ const piece = square.piece;
252
+ const duration = animate ? this.config.dropCenterTime : 0;
253
+ console.debug(`[PieceService] centerPiece: ${piece.id} on ${square.id}`);
254
+ piece.translate(
255
+ square,
256
+ duration,
257
+ this._getTransitionTimingFunction(),
258
+ this.config.dropCenterAnimation,
259
+ () => {
260
+ // After animation, reset all drag-related styles
261
+ if (!piece.element) return;
262
+ piece.element.style.position = '';
263
+ piece.element.style.left = '';
264
+ piece.element.style.top = '';
265
+ piece.element.style.transform = '';
266
+ piece.element.style.zIndex = '';
267
+ }
268
+ );
269
+ }
270
+
271
+ /**
272
+ * Gets the transition timing function for animations
273
+ * @private
274
+ * @returns {Function} Timing function
275
+ */
276
+ _getTransitionTimingFunction() {
277
+ return (elapsed, duration, type = 'ease') => {
278
+ const x = elapsed / duration;
279
+
280
+ switch (type) {
281
+ case 'linear':
282
+ return x;
283
+ case 'ease':
284
+ return (x ** 2) * (3 - 2 * x);
285
+ case 'ease-in':
286
+ return x ** 2;
287
+ case 'ease-out':
288
+ return -1 * (x - 1) ** 2 + 1;
289
+ case 'ease-in-out':
290
+ return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
291
+ default:
292
+ return x;
293
+ }
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Cleans up resources
299
+ */
300
+ destroy() {
301
+ // Cleanup any cached pieces or references
302
+ }
303
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Service for managing chess positions and FEN conversion
3
+ * @module services/PositionService
4
+ * @since 2.0.0
5
+ */
6
+
7
+ import { Chess, validateFen } from '../utils/chess.js';
8
+ import { STANDARD_POSITIONS, DEFAULT_STARTING_POSITION, BOARD_LETTERS } from '../constants/positions.js';
9
+ import { ValidationError } from '../errors/ChessboardError.js';
10
+ import { ERROR_MESSAGES } from '../errors/messages.js';
11
+
12
+ /**
13
+ * Service responsible for position management and FEN operations
14
+ * @class
15
+ */
16
+ export class PositionService {
17
+ /**
18
+ * Creates a new PositionService instance
19
+ * @param {ChessboardConfig} config - Board configuration
20
+ */
21
+ constructor(config) {
22
+ this.config = config;
23
+ this.game = null;
24
+ }
25
+
26
+ /**
27
+ * Converts various position formats to FEN string
28
+ * @param {string|Object} position - Position in various formats
29
+ * @returns {string} FEN string representation
30
+ * @throws {ValidationError} When position format is invalid
31
+ */
32
+ convertFen(position) {
33
+ if (typeof position === 'string') {
34
+ return this._convertStringPosition(position);
35
+ } else if (typeof position === 'object' && position !== null) {
36
+ return this._convertObjectPosition(position);
37
+ } else {
38
+ throw new ValidationError(ERROR_MESSAGES.invalid_position + position, 'position', position);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Converts string position to FEN
44
+ * @private
45
+ * @param {string} position - String position
46
+ * @returns {string} FEN string
47
+ */
48
+ _convertStringPosition(position) {
49
+ if (position === 'start') {
50
+ return DEFAULT_STARTING_POSITION;
51
+ }
52
+
53
+ if (this.validateFen(position)) {
54
+ return position;
55
+ }
56
+
57
+ if (STANDARD_POSITIONS[position]) {
58
+ return STANDARD_POSITIONS[position];
59
+ }
60
+
61
+ throw new ValidationError(ERROR_MESSAGES.invalid_position + position, 'position', position);
62
+ }
63
+
64
+ /**
65
+ * Converts object position to FEN
66
+ * @private
67
+ * @param {Object} position - Object with square->piece mapping
68
+ * @returns {string} FEN string
69
+ */
70
+ _convertObjectPosition(position) {
71
+ const parts = [];
72
+
73
+ for (let row = 0; row < 8; row++) {
74
+ const rowParts = [];
75
+ let empty = 0;
76
+
77
+ for (let col = 0; col < 8; col++) {
78
+ const square = this._getSquareID(row, col);
79
+ const piece = position[square];
80
+
81
+ if (piece) {
82
+ if (empty > 0) {
83
+ rowParts.push(empty);
84
+ empty = 0;
85
+ }
86
+ // Convert piece notation: white pieces become uppercase, black remain lowercase
87
+ const fenPiece = piece[1] === 'w' ? piece[0].toUpperCase() : piece[0].toLowerCase();
88
+ rowParts.push(fenPiece);
89
+ } else {
90
+ empty++;
91
+ }
92
+ }
93
+
94
+ if (empty > 0) {
95
+ rowParts.push(empty);
96
+ }
97
+
98
+ parts.push(rowParts.join(''));
99
+ }
100
+
101
+ return parts.join('/') + ' w KQkq - 0 1';
102
+ }
103
+
104
+ /**
105
+ * Sets up the game with the given position
106
+ * @param {string|Object} position - Position to set
107
+ * @param {Object} [options] - Additional options for game setup
108
+ */
109
+ setGame(position, options = {}) {
110
+ const fen = this.convertFen(position);
111
+
112
+ if (this.game) {
113
+ this.game.load(fen, options);
114
+ } else {
115
+ this.game = new Chess(fen);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Gets the current game instance
121
+ * @returns {Chess} Current chess.js game instance
122
+ */
123
+ getGame() {
124
+ return this.game;
125
+ }
126
+
127
+ /**
128
+ * Validates a FEN string
129
+ * @param {string} fen - FEN string to validate
130
+ * @returns {boolean} True if valid, false otherwise
131
+ */
132
+ validateFen(fen) {
133
+ return validateFen(fen);
134
+ }
135
+
136
+ /**
137
+ * Gets piece information for a specific square
138
+ * @param {string} squareId - Square identifier
139
+ * @returns {string|null} Piece ID or null if no piece
140
+ */
141
+ getGamePieceId(squareId) {
142
+ if (!this.game) return null;
143
+ const piece = this.game.get(squareId);
144
+ return piece ? piece.color + piece.type : null;
145
+ }
146
+
147
+ /**
148
+ * Checks if a specific piece is on a specific square
149
+ * @param {string} piece - Piece ID to check
150
+ * @param {string} square - Square to check
151
+ * @returns {boolean} True if piece is on square
152
+ */
153
+ isPiece(piece, square) {
154
+ return this.getGamePieceId(square) === piece;
155
+ }
156
+
157
+ /**
158
+ * Converts board coordinates to square ID
159
+ * @private
160
+ * @param {number} row - Row index (0-7)
161
+ * @param {number} col - Column index (0-7)
162
+ * @returns {string} Square ID (e.g., 'e4')
163
+ */
164
+ _getSquareID(row, col) {
165
+ row = parseInt(row);
166
+ col = parseInt(col);
167
+
168
+ if (this.config.orientation === 'w') {
169
+ row = 8 - row;
170
+ col = col + 1;
171
+ } else {
172
+ row = row + 1;
173
+ col = 8 - col;
174
+ }
175
+
176
+ const letter = BOARD_LETTERS[col - 1];
177
+ return letter + row;
178
+ }
179
+
180
+ /**
181
+ * Changes the turn in a FEN string
182
+ * @param {string} fen - Original FEN string
183
+ * @param {string} color - New turn color ('w' or 'b')
184
+ * @returns {string} Modified FEN string
185
+ */
186
+ changeFenTurn(fen, color) {
187
+ const parts = fen.split(' ');
188
+ parts[1] = color;
189
+ return parts.join(' ');
190
+ }
191
+
192
+ /**
193
+ * Gets the current position as an object
194
+ * @returns {Object} Position object with piece placements
195
+ */
196
+ getPosition() {
197
+ const position = {};
198
+ const game = this.getGame();
199
+
200
+ // Convert chess.js board to position object
201
+ const squares = ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1',
202
+ 'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
203
+ 'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
204
+ 'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
205
+ 'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
206
+ 'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
207
+ 'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
208
+ 'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8'];
209
+
210
+ for (const square of squares) {
211
+ const piece = game.get(square);
212
+ if (piece) {
213
+ position[square] = piece.type + piece.color;
214
+ }
215
+ }
216
+
217
+ return position;
218
+ }
219
+
220
+ /**
221
+ * Toggles the turn in a FEN string
222
+ * @param {string} fen - Original FEN string
223
+ * @returns {string} Modified FEN string
224
+ */
225
+ changeFenColor(fen) {
226
+ const parts = fen.split(' ');
227
+ parts[1] = parts[1] === 'w' ? 'b' : 'w';
228
+ return parts.join(' ');
229
+ }
230
+
231
+ /**
232
+ * Cleans up resources
233
+ */
234
+ destroy() {
235
+ this.game = null;
236
+ }
237
+ }