@alepot55/chessboardjs 2.3.3 → 2.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -3,17 +3,14 @@
3
3
  "chess.js": "^1.0.0"
4
4
  },
5
5
  "name": "@alepot55/chessboardjs",
6
- "version": "2.3.3",
6
+ "version": "2.3.5",
7
7
  "main": "src/index.js",
8
8
  "type": "module",
9
9
  "scripts": {
10
10
  "dev": "rollup -c config/rollup.config.js --watch",
11
- "build": "rollup -c config/rollup.config.js",
12
- "build:all": "npm run build:esm && npm run build:cjs && npm run build:umd && npm run build:iife",
13
- "build:esm": "rollup -c config/rollup.config.js --format esm",
14
- "build:cjs": "rollup -c config/rollup.config.js --format cjs",
15
- "build:umd": "rollup -c config/rollup.config.js --format umd",
16
- "build:iife": "rollup -c config/rollup.config.js --format iife",
11
+ "build:css": "cat src/styles/board.css src/styles/pieces.css src/styles/animations.css > dist/chessboard.css",
12
+ "build:rollup": "rollup -c config/rollup.config.js",
13
+ "build": "npm run build:css && npm run build:rollup",
17
14
  "test": "jest --config config/jest.config.js",
18
15
  "test:watch": "jest --config config/jest.config.js --watch",
19
16
  "test:coverage": "jest --config config/jest.config.js --coverage",
@@ -46,6 +43,7 @@
46
43
  "babel-jest": "^29.7.0",
47
44
  "jest": "^29.7.0",
48
45
  "jest-environment-jsdom": "^29.7.0",
49
- "rollup": "^4.34.7"
46
+ "rollup": "^4.34.7",
47
+ "terser": "^5.43.1"
50
48
  }
51
49
  }
@@ -194,6 +194,8 @@ class Piece {
194
194
  if (callback) callback();
195
195
  }
196
196
  }
197
+ // Se l'elemento è già stato rimosso, esci subito
198
+ if (!this.element) { console.debug(`[Piece] fadeOut: ${this.id} - element is null (init)`); if (callback) callback(); return; }
197
199
  fade();
198
200
  }
199
201
 
@@ -201,24 +203,22 @@ class Piece {
201
203
  if (!this.element) { console.debug(`[Piece] setDrag: ${this.id} - element is null`); return; }
202
204
  this.element.ondragstart = (e) => { e.preventDefault() };
203
205
  this.element.onmousedown = f;
206
+ this.element.ontouchstart = f; // Drag touch
204
207
  console.debug(`[Piece] setDrag: ${this.id}`);
205
208
  }
206
209
 
207
210
  destroy() {
211
+ if (!this.element) return; // Idempotente: già rimosso
208
212
  console.debug(`[Piece] Destroy: ${this.id}`);
209
213
  // Remove all event listeners
210
- if (this.element) {
211
- this.element.onmousedown = null;
212
- this.element.ondragstart = null;
213
-
214
- // Remove from DOM
215
- if (this.element.parentNode) {
216
- this.element.parentNode.removeChild(this.element);
217
- }
218
-
219
- // Clear references
220
- this.element = null;
214
+ this.element.onmousedown = null;
215
+ this.element.ondragstart = null;
216
+ // Remove from DOM
217
+ if (this.element.parentNode) {
218
+ this.element.parentNode.removeChild(this.element);
221
219
  }
220
+ // Clear references
221
+ this.element = null;
222
222
  }
223
223
 
224
224
  translate(to, duration, transition_f, speed, callback = null) {
@@ -73,8 +73,8 @@ class Square {
73
73
  // Best practice: destroy the piece object if present
74
74
  if (this.piece && typeof this.piece.destroy === 'function') {
75
75
  this.piece.destroy();
76
- this.piece = null;
77
76
  }
77
+ this.piece = null;
78
78
  // Remove any orphaned img.piece elements from the DOM
79
79
  const pieceElements = this.element.querySelectorAll('img.piece');
80
80
  pieceElements.forEach(element => {
@@ -656,68 +656,48 @@ class Chessboard {
656
656
  * @param {boolean} [isPositionLoad=false] - Whether this is a position load (affects delay)
657
657
  */
658
658
  _doUpdateBoardPieces(animation = false, isPositionLoad = false) {
659
+ // Blocca update se un drag è in corso
660
+ if (this._isDragging) return;
659
661
  // Skip update if we're in the middle of a promotion
660
662
  if (this._isPromoting) {
661
- console.log('Skipping board update during promotion');
662
663
  return;
663
664
  }
664
-
665
- // Check if services are available
666
665
  if (!this.positionService || !this.positionService.getGame()) {
667
- console.log('Cannot update board pieces - position service not available');
668
666
  return;
669
667
  }
670
-
671
668
  const squares = this.boardService.getAllSquares();
672
669
  const gameStateBefore = this.positionService.getGame().fen();
673
-
674
- // PATCH ROBUSTA: se la board è completamente vuota, forza la rimozione di TUTTI i pezzi
675
670
  if (/^8\/8\/8\/8\/8\/8\/8\/8/.test(gameStateBefore)) {
676
- console.log('Board vuota rilevata - rimozione forzata di tutti i pezzi dal DOM');
677
-
678
- // 1. Rimuovi tutti gli elementi DOM dei pezzi dal contenitore della board
679
671
  const boardContainer = document.getElementById(this.config.id_div);
680
672
  if (boardContainer) {
681
673
  const pieceElements = boardContainer.querySelectorAll('.piece');
682
674
  pieceElements.forEach(element => {
683
- console.log('Rimozione forzata elemento DOM pezzo:', element);
684
675
  element.remove();
685
676
  });
686
677
  }
687
-
688
- // 2. Azzera tutti i riferimenti JS ai pezzi
689
678
  Object.values(squares).forEach(sq => {
690
679
  if (sq && sq.piece) {
691
- console.log('Azzero riferimento pezzo su casella:', sq.id);
692
680
  sq.piece = null;
693
681
  }
694
682
  });
695
-
696
- // 3. Forza la pulizia di eventuali selezioni/hint
697
683
  this._clearVisualState();
698
-
699
- // 4. Aggiungi i listener e notifica il cambio
700
684
  this._addListeners();
701
685
  if (this.config.onChange) this.config.onChange(gameStateBefore);
702
-
703
- console.log('Rimozione forzata completata');
704
686
  return;
705
687
  }
706
-
707
- console.log('_doUpdateBoardPieces - current FEN:', gameStateBefore);
708
- console.log('_doUpdateBoardPieces - animation:', animation, 'style:', this.config.animationStyle, 'isPositionLoad:', isPositionLoad);
709
-
710
- // Determine which animation style to use
711
688
  const useSimultaneous = this.config.animationStyle === 'simultaneous';
712
- console.log('_doUpdateBoardPieces - useSimultaneous:', useSimultaneous);
713
-
714
689
  if (useSimultaneous) {
715
- console.log('Using simultaneous animation');
716
690
  this._doSimultaneousUpdate(squares, gameStateBefore, isPositionLoad);
717
691
  } else {
718
- console.log('Using sequential animation');
719
692
  this._doSequentialUpdate(squares, gameStateBefore, animation);
720
693
  }
694
+ // Pulizia finale robusta: rimuovi tutti i pezzi orfani dal DOM e dal riferimento JS
695
+ Object.values(this.boardService.getAllSquares()).forEach(square => {
696
+ const expectedPieceId = this.positionService.getGamePieceId(square.id);
697
+ if (!expectedPieceId && typeof square.forceRemoveAllPieces === 'function') {
698
+ square.forceRemoveAllPieces();
699
+ }
700
+ });
721
701
  }
722
702
 
723
703
  /**
@@ -746,7 +726,12 @@ class Chessboard {
746
726
 
747
727
  // Se c'è un pezzo attuale ma non è quello atteso, rimuovilo
748
728
  if (currentPiece && currentPieceId !== expectedPieceId) {
749
- this.pieceService.removePieceFromSquare(square, animation);
729
+ // Rimozione robusta: elimina tutti i pezzi orfani dal DOM e dal riferimento JS
730
+ if (typeof square.forceRemoveAllPieces === 'function') {
731
+ square.forceRemoveAllPieces();
732
+ } else {
733
+ this.pieceService.removePieceFromSquare(square, animation);
734
+ }
750
735
  }
751
736
 
752
737
  // Se c'è un pezzo atteso ma non è quello attuale, aggiungilo
@@ -875,7 +860,13 @@ class Chessboard {
875
860
  for (let i = 0; i < fromList.length; i++) {
876
861
  if (!fromMatched[i]) {
877
862
  setTimeout(() => {
878
- this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
863
+ // Rimozione robusta: elimina tutti i pezzi orfani dal DOM e dal riferimento JS
864
+ if (typeof fromList[i].square.forceRemoveAllPieces === 'function') {
865
+ fromList[i].square.forceRemoveAllPieces();
866
+ } else {
867
+ this.pieceService.removePieceFromSquare(fromList[i].square, true, onAnimationComplete);
868
+ }
869
+ onAnimationComplete();
879
870
  }, animationIndex * animationDelay);
880
871
  animationIndex++;
881
872
  }
@@ -1267,6 +1258,8 @@ class Chessboard {
1267
1258
  if (this._updateBoardPieces) {
1268
1259
  this._updateBoardPieces(animate, true);
1269
1260
  }
1261
+ // Forza la sincronizzazione dopo setPosition
1262
+ this._updateBoardPieces(true, false);
1270
1263
  return true;
1271
1264
  }
1272
1265
  /**
@@ -1280,7 +1273,10 @@ class Chessboard {
1280
1273
  // Use the default starting position from config or fallback
1281
1274
  const startPosition = this.config && this.config.position ? this.config.position : 'start';
1282
1275
  this._updateBoardPieces(animate);
1283
- return this.setPosition(startPosition, { animate });
1276
+ const result = this.setPosition(startPosition, { animate });
1277
+ // Forza la sincronizzazione dopo reset
1278
+ this._updateBoardPieces(true, false);
1279
+ return result;
1284
1280
  }
1285
1281
  /**
1286
1282
  * Clear the board
@@ -1304,6 +1300,8 @@ class Chessboard {
1304
1300
  if (this._updateBoardPieces) {
1305
1301
  this._updateBoardPieces(animate, true);
1306
1302
  }
1303
+ // Forza la sincronizzazione dopo clear
1304
+ this._updateBoardPieces(true, false);
1307
1305
  return true;
1308
1306
  }
1309
1307
 
@@ -1318,7 +1316,8 @@ class Chessboard {
1318
1316
  const undone = this.positionService.getGame().undo();
1319
1317
  if (undone) {
1320
1318
  this._undoneMoves.push(undone);
1321
- this._updateBoardPieces(opts.animate !== false);
1319
+ // Forza refresh completo di tutti i pezzi dopo undo
1320
+ this._updateBoardPieces(true, true);
1322
1321
  return undone;
1323
1322
  }
1324
1323
  return null;
@@ -1335,7 +1334,8 @@ class Chessboard {
1335
1334
  const moveObj = { from: move.from, to: move.to };
1336
1335
  if (move.promotion) moveObj.promotion = move.promotion;
1337
1336
  const result = this.positionService.getGame().move(moveObj);
1338
- this._updateBoardPieces(opts.animate !== false);
1337
+ // Forza refresh completo di tutti i pezzi dopo redo
1338
+ this._updateBoardPieces(true, true);
1339
1339
  return result;
1340
1340
  }
1341
1341
  return false;
@@ -1354,17 +1354,19 @@ class Chessboard {
1354
1354
  * @returns {string|null}
1355
1355
  */
1356
1356
  getPiece(square) {
1357
- // Restituisce sempre 'wq' (colore prima, tipo dopo, lowercase) o null
1358
- const sq = this.boardService.getSquare(square);
1359
- if (!sq) return null;
1360
- const piece = sq.piece;
1357
+ // Sempre leggi lo stato aggiornato dal boardService
1358
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1359
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[getPiece] Parametro square non valido');
1360
+ // Forza sync prima di leggere
1361
+ this._updateBoardPieces(false, false);
1362
+ const piece = squareObj.piece;
1361
1363
  if (!piece) return null;
1362
1364
  return (piece.color + piece.type).toLowerCase();
1363
1365
  }
1364
1366
  /**
1365
1367
  * Put a piece on a square
1366
- * @param {string} piece
1367
- * @param {string} square
1368
+ * @param {string|Piece} piece
1369
+ * @param {string|Square} square
1368
1370
  * @param {Object} [opts]
1369
1371
  * @param {boolean} [opts.animate=true]
1370
1372
  * @returns {boolean}
@@ -1375,7 +1377,6 @@ class Chessboard {
1375
1377
  if (typeof piece === 'object' && piece.type && piece.color) {
1376
1378
  pieceStr = (piece.color + piece.type).toLowerCase();
1377
1379
  } else if (typeof piece === 'string' && piece.length === 2) {
1378
- // Accetta sia 'wq' che 'qw', normalizza a 'wq'
1379
1380
  const a = piece[0].toLowerCase();
1380
1381
  const b = piece[1].toLowerCase();
1381
1382
  const types = 'kqrbnp';
@@ -1388,42 +1389,36 @@ class Chessboard {
1388
1389
  throw new Error(`[putPiece] Invalid piece: ${piece}`);
1389
1390
  }
1390
1391
  }
1391
- const validSquare = this.validationService.isValidSquare(square);
1392
- const validPiece = this.validationService.isValidPiece(pieceStr);
1393
- if (!validSquare) throw new Error(`[putPiece] Invalid square: ${square}`);
1394
- if (!validPiece) throw new Error(`[putPiece] Invalid piece: ${pieceStr}`);
1395
- if (!this.positionService || !this.positionService.getGame()) {
1396
- throw new Error('[putPiece] No positionService or game');
1397
- }
1392
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1393
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[putPiece] Parametro square non valido');
1398
1394
  const pieceObj = this.pieceService.convertPiece(pieceStr);
1399
- const squareObj = this.boardService.getSquare(square);
1400
- if (!squareObj) throw new Error(`[putPiece] Square not found: ${square}`);
1401
- squareObj.piece = pieceObj;
1395
+ if (!pieceObj || typeof pieceObj !== 'object' || !('type' in pieceObj)) throw new Error('[putPiece] Parametro piece non valido');
1396
+ // Aggiorna solo il motore chess.js
1402
1397
  const chessJsPiece = { type: pieceObj.type, color: pieceObj.color };
1403
1398
  const game = this.positionService.getGame();
1404
- const result = game.put(chessJsPiece, square);
1405
- if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${square}`);
1399
+ const result = game.put(chessJsPiece, squareObj.id);
1400
+ if (!result) throw new Error(`[putPiece] Game.put failed for ${pieceStr} on ${squareObj.id}`);
1401
+ // Non aggiornare direttamente square.piece!
1402
+ // Riallinea la board JS allo stato del motore
1406
1403
  this._updateBoardPieces(animate);
1407
1404
  return true;
1408
1405
  }
1409
1406
  /**
1410
1407
  * Remove a piece from a square
1411
- * @param {string} square
1408
+ * @param {string|Square} square
1412
1409
  * @param {Object} [opts]
1413
1410
  * @param {boolean} [opts.animate=true]
1414
1411
  * @returns {string|null}
1415
1412
  */
1416
1413
  removePiece(square, opts = {}) {
1417
1414
  const animate = opts.animate !== undefined ? opts.animate : true;
1418
- if (!this.validationService.isValidSquare(square)) {
1419
- throw new Error(`[removePiece] Invalid square: ${square}`);
1420
- }
1421
- const squareObj = this.boardService.getSquare(square);
1422
- if (!squareObj) return true;
1423
- if (!squareObj.piece) return true;
1424
- squareObj.piece = null;
1415
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1416
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[removePiece] Parametro square non valido');
1417
+ // Aggiorna solo il motore chess.js
1425
1418
  const game = this.positionService.getGame();
1426
- game.remove(square);
1419
+ game.remove(squareObj.id);
1420
+ // Non aggiornare direttamente square.piece!
1421
+ // Riallinea la board JS allo stato del motore
1427
1422
  this._updateBoardPieces(animate);
1428
1423
  return true;
1429
1424
  }
@@ -1488,28 +1483,32 @@ class Chessboard {
1488
1483
  // --- HIGHLIGHTING & UI ---
1489
1484
  /**
1490
1485
  * Highlight a square
1491
- * @param {string} square
1486
+ * @param {string|Square} square
1492
1487
  * @param {Object} [opts]
1493
1488
  */
1494
1489
  highlight(square, opts = {}) {
1495
- if (!this.validationService.isValidSquare(square)) return;
1490
+ // API: accetta id, converte subito in oggetto
1491
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1492
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[highlight] Parametro square non valido');
1496
1493
  if (this.boardService && this.boardService.highlightSquare) {
1497
- this.boardService.highlightSquare(square, opts);
1494
+ this.boardService.highlightSquare(squareObj, opts);
1498
1495
  } else if (this.eventService && this.eventService.highlightSquare) {
1499
- this.eventService.highlightSquare(square, opts);
1496
+ this.eventService.highlightSquare(squareObj, opts);
1500
1497
  }
1501
1498
  }
1502
1499
  /**
1503
1500
  * Remove highlight from a square
1504
- * @param {string} square
1501
+ * @param {string|Square} square
1505
1502
  * @param {Object} [opts]
1506
1503
  */
1507
1504
  dehighlight(square, opts = {}) {
1508
- if (!this.validationService.isValidSquare(square)) return;
1505
+ // API: accetta id, converte subito in oggetto
1506
+ const squareObj = typeof square === 'string' ? this.boardService.getSquare(square) : square;
1507
+ if (!squareObj || typeof squareObj !== 'object' || !('id' in squareObj)) throw new Error('[dehighlight] Parametro square non valido');
1509
1508
  if (this.boardService && this.boardService.dehighlightSquare) {
1510
- this.boardService.dehighlightSquare(square, opts);
1509
+ this.boardService.dehighlightSquare(squareObj, opts);
1511
1510
  } else if (this.eventService && this.eventService.dehighlightSquare) {
1512
- this.eventService.dehighlightSquare(square, opts);
1511
+ this.eventService.dehighlightSquare(squareObj, opts);
1513
1512
  }
1514
1513
  }
1515
1514
 
@@ -1534,6 +1533,8 @@ class Chessboard {
1534
1533
  * @returns {boolean}
1535
1534
  */
1536
1535
  isGameOver() {
1536
+ // Forza sync prima di interrogare il motore
1537
+ this._updateBoardPieces(false, false);
1537
1538
  const game = this.positionService.getGame();
1538
1539
  if (!game) return false;
1539
1540
  if (game.isGameOver) return game.isGameOver();
@@ -1903,7 +1904,7 @@ class Chessboard {
1903
1904
  dehighlightSquare(square) {
1904
1905
  return this.boardService.dehighlight(square);
1905
1906
  }
1906
- forceSync() { this._updateBoardPieces(true, true); }
1907
+ forceSync() { this._updateBoardPieces(true, true); this._updateBoardPieces(true, false); }
1907
1908
 
1908
1909
  // Metodi mancanti che causano fallimenti nei test
1909
1910
  /**
@@ -1916,37 +1917,37 @@ class Chessboard {
1916
1917
  movePiece(move, opts = {}) {
1917
1918
  const animate = opts.animate !== undefined ? opts.animate : true;
1918
1919
 
1919
- // Parse move string if needed
1920
- let fromSquare, toSquare, promotion;
1920
+ // --- API: accetta id/stringhe, ma converte subito in oggetti ---
1921
+ let fromSquareObj, toSquareObj, promotion;
1921
1922
  if (typeof move === 'string') {
1922
1923
  if (move.length === 4) {
1923
- fromSquare = move.substring(0, 2);
1924
- toSquare = move.substring(2, 4);
1924
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
1925
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
1925
1926
  } else if (move.length === 5) {
1926
- fromSquare = move.substring(0, 2);
1927
- toSquare = move.substring(2, 4);
1927
+ fromSquareObj = this.boardService.getSquare(move.substring(0, 2));
1928
+ toSquareObj = this.boardService.getSquare(move.substring(2, 4));
1928
1929
  promotion = move.substring(4, 5);
1929
1930
  } else {
1930
1931
  throw new Error(`Invalid move format: ${move}`);
1931
1932
  }
1932
1933
  } else if (typeof move === 'object' && move.from && move.to) {
1933
- fromSquare = move.from;
1934
- toSquare = move.to;
1934
+ // Se sono id, converto in oggetti; se sono già oggetti, li uso direttamente
1935
+ fromSquareObj = typeof move.from === 'string' ? this.boardService.getSquare(move.from) : move.from;
1936
+ toSquareObj = typeof move.to === 'string' ? this.boardService.getSquare(move.to) : move.to;
1935
1937
  promotion = move.promotion;
1936
1938
  } else {
1937
1939
  throw new Error(`Invalid move: ${move}`);
1938
1940
  }
1939
1941
 
1940
- // Get square objects
1941
- const fromSquareObj = this.boardService.getSquare(fromSquare);
1942
- const toSquareObj = this.boardService.getSquare(toSquare);
1943
-
1944
1942
  if (!fromSquareObj || !toSquareObj) {
1945
- throw new Error(`Invalid squares: ${fromSquare} or ${toSquare}`);
1943
+ throw new Error(`Invalid squares: ${move.from || move.substring(0, 2)} or ${move.to || move.substring(2, 4)}`);
1946
1944
  }
1947
1945
 
1948
- // Execute the move
1949
- return this._onMove(fromSquareObj, toSquareObj, promotion, animate);
1946
+ // --- Internamente: lavora solo con oggetti ---
1947
+ const result = this._onMove(fromSquareObj, toSquareObj, promotion, animate);
1948
+ // Dopo ogni mossa, forza la sincronizzazione della board
1949
+ this._updateBoardPieces(true, false);
1950
+ return result;
1950
1951
  }
1951
1952
 
1952
1953
  // Aliases for backward compatibility
@@ -116,13 +116,32 @@ export class BoardService {
116
116
 
117
117
  /**
118
118
  * Gets a square by its ID
119
- * @param {string} squareId - Square identifier (e.g., 'e4')
119
+ * @param {string} squareId - Square identifier (API pubblica)
120
120
  * @returns {Square|null} The square or null if not found
121
121
  */
122
122
  getSquare(squareId) {
123
123
  return this.squares[squareId] || null;
124
124
  }
125
125
 
126
+ /**
127
+ * Highlight a square (solo oggetto)
128
+ * @param {Square} square
129
+ * @param {Object} [opts]
130
+ */
131
+ highlightSquare(square, opts = {}) {
132
+ if (!square) throw new Error('highlightSquare richiede oggetto Square');
133
+ // ... logica esistente ...
134
+ }
135
+ /**
136
+ * Dehighlight a square (solo oggetto)
137
+ * @param {Square} square
138
+ * @param {Object} [opts]
139
+ */
140
+ dehighlightSquare(square, opts = {}) {
141
+ if (!square) throw new Error('dehighlightSquare richiede oggetto Square');
142
+ // ... logica esistente ...
143
+ }
144
+
126
145
  /**
127
146
  * Gets all squares
128
147
  * @returns {Object.<string, Square>} All squares indexed by ID