@alepot55/chessboardjs 2.2.1 → 2.2.2

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 (80) hide show
  1. package/.eslintrc.json +227 -0
  2. package/README.md +125 -401
  3. package/assets/themes/alepot/theme.json +42 -0
  4. package/assets/themes/default/theme.json +42 -0
  5. package/chessboard.bundle.js +708 -58
  6. package/config/jest.config.js +15 -0
  7. package/config/rollup.config.js +36 -0
  8. package/dist/chessboard.cjs.js +10690 -0
  9. package/dist/chessboard.css +228 -0
  10. package/dist/chessboard.esm.js +10621 -0
  11. package/dist/chessboard.iife.js +10696 -0
  12. package/dist/chessboard.umd.js +10696 -0
  13. package/jest.config.js +2 -7
  14. package/package.json +18 -3
  15. package/rollup.config.js +2 -11
  16. package/{chessboard.move.js → src/components/Move.js} +3 -3
  17. package/src/components/Piece.js +288 -0
  18. package/{chessboard.square.js → src/components/Square.js} +60 -7
  19. package/src/constants/index.js +15 -0
  20. package/src/constants/positions.js +62 -0
  21. package/src/core/Chessboard.js +1939 -0
  22. package/src/core/ChessboardConfig.js +458 -0
  23. package/src/core/ChessboardFactory.js +385 -0
  24. package/src/core/index.js +141 -0
  25. package/src/errors/ChessboardError.js +133 -0
  26. package/src/errors/index.js +15 -0
  27. package/src/errors/messages.js +189 -0
  28. package/src/index.js +103 -0
  29. package/src/services/AnimationService.js +180 -0
  30. package/src/services/BoardService.js +156 -0
  31. package/src/services/CoordinateService.js +355 -0
  32. package/src/services/EventService.js +955 -0
  33. package/src/services/MoveService.js +629 -0
  34. package/src/services/PieceService.js +312 -0
  35. package/src/services/PositionService.js +237 -0
  36. package/src/services/ValidationService.js +673 -0
  37. package/src/services/index.js +14 -0
  38. package/src/styles/animations.css +46 -0
  39. package/{chessboard.css → src/styles/board.css} +8 -4
  40. package/src/styles/index.css +4 -0
  41. package/src/styles/pieces.css +70 -0
  42. package/src/utils/animations.js +37 -0
  43. package/{chess.js → src/utils/chess.js} +16 -16
  44. package/src/utils/coordinates.js +62 -0
  45. package/src/utils/cross-browser.js +150 -0
  46. package/src/utils/logger.js +422 -0
  47. package/src/utils/performance.js +311 -0
  48. package/src/utils/validation.js +458 -0
  49. package/tests/unit/chessboard-config-animations.test.js +106 -0
  50. package/tests/unit/chessboard-robust.test.js +163 -0
  51. package/tests/unit/chessboard.test.js +183 -0
  52. package/chessboard.config.js +0 -147
  53. package/chessboard.js +0 -979
  54. package/chessboard.piece.js +0 -115
  55. package/test/chessboard.test.js +0 -128
  56. /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
  57. /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
  58. /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
  59. /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
  60. /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
  61. /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
  62. /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
  63. /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
  64. /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
  65. /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
  66. /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
  67. /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
  68. /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
  69. /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
  70. /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
  71. /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
  72. /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
  73. /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
  74. /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
  75. /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
  76. /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
  77. /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
  78. /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
  79. /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
  80. /package/{.babelrc → config/.babelrc} +0 -0
@@ -1,4 +1,4 @@
1
- var Chessboard = (function () {
1
+ var Chessboard = (function (exports) {
2
2
  'use strict';
3
3
 
4
4
  /**
@@ -1992,6 +1992,14 @@ var Chessboard = (function () {
1992
1992
  'veryFast': 100
1993
1993
  };
1994
1994
 
1995
+ const boolValues = {
1996
+ 'true': true,
1997
+ 'false': false,
1998
+ 'none': false,
1999
+ 1: true,
2000
+ 0: false
2001
+ };
2002
+
1995
2003
  const transitionFunctions = {
1996
2004
  'ease': 'ease',
1997
2005
  'linear': 'linear',
@@ -2023,7 +2031,7 @@ var Chessboard = (function () {
2023
2031
  fadeTime: 'fast',
2024
2032
  fadeAnimation: 'ease',
2025
2033
  ratio: 0.9,
2026
- piecesPath: 'https://cdn.jsdelivr.net/npm/@alepot55/chessboardjs/default_pieces',
2034
+ piecesPath: '../assets/themes/default',
2027
2035
  onMove: () => true,
2028
2036
  onMoveEnd: () => true,
2029
2037
  onChange: () => true,
@@ -2113,11 +2121,26 @@ var Chessboard = (function () {
2113
2121
 
2114
2122
  setBoolean(value) {
2115
2123
  if (typeof value === 'boolean') return value;
2124
+ if (value in boolValues) return boolValues[value];
2116
2125
  throw new Error('Invalid boolean value');
2117
2126
  }
2118
2127
 
2119
2128
  setTransitionFunction(value) {
2120
- if (Object.keys(transitionFunctions).indexOf(value) !== -1) return transitionFunctions[value];
2129
+ // Handle boolean values - true means use default 'ease', false/null means no animation
2130
+ if (typeof value === 'boolean') {
2131
+ return value ? transitionFunctions['ease'] : null;
2132
+ }
2133
+
2134
+ // Handle string values
2135
+ if (typeof value === 'string' && value in transitionFunctions) {
2136
+ return transitionFunctions[value];
2137
+ }
2138
+
2139
+ // Handle null/undefined
2140
+ if (value === null || value === undefined) {
2141
+ return null;
2142
+ }
2143
+
2121
2144
  throw new Error('Invalid transition function');
2122
2145
  }
2123
2146
  }
@@ -2166,7 +2189,20 @@ var Chessboard = (function () {
2166
2189
  }
2167
2190
 
2168
2191
  fadeOut(duration, speed, transition_f) {
2169
- performance.now();
2192
+ let start = performance.now();
2193
+ let opacity = 1;
2194
+ let piece = this;
2195
+ let fade = function () {
2196
+ let elapsed = performance.now() - start;
2197
+ opacity = 1 - transition_f(elapsed, duration, speed);
2198
+ piece.element.style.opacity = opacity;
2199
+ if (elapsed < duration) {
2200
+ requestAnimationFrame(fade);
2201
+ } else {
2202
+ piece.element.style.opacity = 0;
2203
+ }
2204
+ };
2205
+ fade();
2170
2206
  }
2171
2207
 
2172
2208
  setDrag(f) {
@@ -2279,6 +2315,9 @@ var Chessboard = (function () {
2279
2315
  }
2280
2316
 
2281
2317
  removePiece() {
2318
+ if (!this.piece) {
2319
+ return null;
2320
+ }
2282
2321
  this.element.removeChild(this.piece.element);
2283
2322
  const piece = this.piece;
2284
2323
  this.piece = null;
@@ -2408,7 +2447,7 @@ var Chessboard = (function () {
2408
2447
  class Move {
2409
2448
 
2410
2449
  constructor(from, to, promotion = null, check = false) {
2411
- this.piece = from.getPiece();
2450
+ this.piece = from ? from.getPiece() : null;
2412
2451
  this.from = from;
2413
2452
  this.to = to;
2414
2453
  this.promotion = promotion;
@@ -2444,7 +2483,136 @@ var Chessboard = (function () {
2444
2483
 
2445
2484
  }
2446
2485
 
2447
- class Chessboard {
2486
+ /**
2487
+ * Performance utilities for smooth interactions
2488
+ */
2489
+
2490
+ /**
2491
+ * Throttle function to limit how often a function can be called
2492
+ * @param {Function} func - Function to throttle
2493
+ * @param {number} limit - Time limit in milliseconds
2494
+ * @returns {Function} Throttled function
2495
+ */
2496
+ function throttle(func, limit) {
2497
+ let inThrottle;
2498
+ return function() {
2499
+ const args = arguments;
2500
+ const context = this;
2501
+ if (!inThrottle) {
2502
+ func.apply(context, args);
2503
+ inThrottle = true;
2504
+ setTimeout(() => inThrottle = false, limit);
2505
+ }
2506
+ };
2507
+ }
2508
+
2509
+ /**
2510
+ * Request animation frame throttle for smooth animations
2511
+ * @param {Function} func - Function to throttle
2512
+ * @returns {Function} RAF throttled function
2513
+ */
2514
+ function rafThrottle(func) {
2515
+ let isThrottled = false;
2516
+ return function() {
2517
+ if (isThrottled) return;
2518
+
2519
+ const args = arguments;
2520
+ const context = this;
2521
+
2522
+ isThrottled = true;
2523
+ requestAnimationFrame(() => {
2524
+ func.apply(context, args);
2525
+ isThrottled = false;
2526
+ });
2527
+ };
2528
+ }
2529
+
2530
+ /**
2531
+ * High performance transform utility
2532
+ * @param {HTMLElement} element - Element to transform
2533
+ * @param {number} x - X coordinate
2534
+ * @param {number} y - Y coordinate
2535
+ * @param {number} scale - Scale factor
2536
+ */
2537
+ function setTransform(element, x, y, scale = 1) {
2538
+ // Use transform3d for hardware acceleration
2539
+ element.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`;
2540
+ }
2541
+
2542
+ /**
2543
+ * Reset element position efficiently
2544
+ * @param {HTMLElement} element - Element to reset
2545
+ */
2546
+ function resetTransform(element) {
2547
+ element.style.transform = '';
2548
+ element.style.left = '';
2549
+ element.style.top = '';
2550
+ }
2551
+
2552
+ /**
2553
+ * Cross-browser utilities for consistent drag & drop behavior
2554
+ */
2555
+
2556
+ /**
2557
+ * Detect browser type and version
2558
+ * @returns {Object} Browser information
2559
+ */
2560
+ function getBrowserInfo() {
2561
+ const ua = navigator.userAgent;
2562
+ const isChrome = ua.includes('Chrome') && !ua.includes('Edg');
2563
+ const isFirefox = ua.includes('Firefox');
2564
+ const isSafari = ua.includes('Safari') && !ua.includes('Chrome');
2565
+ const isEdge = ua.includes('Edg');
2566
+
2567
+ return {
2568
+ isChrome,
2569
+ isFirefox,
2570
+ isSafari,
2571
+ isEdge,
2572
+ devicePixelRatio: window.devicePixelRatio || 1,
2573
+ userAgent: ua
2574
+ };
2575
+ }
2576
+
2577
+ /**
2578
+ * Browser-specific drag optimizations
2579
+ */
2580
+ const DragOptimizations = {
2581
+ /**
2582
+ * Apply browser-specific optimizations to an element
2583
+ * @param {HTMLElement} element - Element to optimize
2584
+ */
2585
+ enableForDrag(element) {
2586
+ const browserInfo = getBrowserInfo();
2587
+
2588
+ // Base optimizations for all browsers
2589
+ element.style.willChange = 'left, top';
2590
+ element.style.pointerEvents = 'none'; // Prevent conflicts
2591
+
2592
+ // Chrome-specific optimizations
2593
+ if (browserInfo.isChrome) {
2594
+ element.style.transform = 'translateZ(0)'; // Force hardware acceleration
2595
+ }
2596
+
2597
+ // Firefox-specific optimizations
2598
+ if (browserInfo.isFirefox) {
2599
+ element.style.backfaceVisibility = 'hidden';
2600
+ }
2601
+ },
2602
+
2603
+ /**
2604
+ * Clean up optimizations after drag
2605
+ * @param {HTMLElement} element - Element to clean up
2606
+ */
2607
+ cleanupAfterDrag(element) {
2608
+ element.style.willChange = 'auto';
2609
+ element.style.pointerEvents = '';
2610
+ element.style.transform = '';
2611
+ element.style.backfaceVisibility = '';
2612
+ }
2613
+ };
2614
+
2615
+ let Chessboard$1 = class Chessboard {
2448
2616
 
2449
2617
  standard_positions = {
2450
2618
  'start': 'start',
@@ -2491,7 +2659,10 @@ var Chessboard = (function () {
2491
2659
  // Initialization
2492
2660
  // -------------------
2493
2661
  constructor(config) {
2662
+ // Debug: log the config to see what we're receiving
2663
+ console.log('Chessboard constructor received config:', config);
2494
2664
  this.config = new ChessboardConfig(config);
2665
+ console.log('Processed config.id_div:', this.config.id_div);
2495
2666
  this.init();
2496
2667
  }
2497
2668
 
@@ -2511,12 +2682,17 @@ var Chessboard = (function () {
2511
2682
  this.clicked = null;
2512
2683
  this.mosseIndietro = [];
2513
2684
  this.clicked = null;
2685
+ this._updateTimeout = null; // For debouncing board updates
2686
+ this._movesCache = new Map(); // Cache per le mosse per migliorare le prestazioni
2687
+ this._cacheTimeout = null; // Timeout per pulire la cache
2688
+ this._isAnimating = false; // Flag to track if animations are in progress
2514
2689
  }
2515
2690
 
2516
2691
  // -------------------
2517
2692
  // Board Setup
2518
2693
  // -------------------
2519
2694
  buildBoard() {
2695
+ console.log('buildBoard: Looking for element with ID:', this.config.id_div, 'Type:', typeof this.config.id_div);
2520
2696
  this.element = document.getElementById(this.config.id_div);
2521
2697
  if (!this.element) {
2522
2698
  throw new Error(this.error_messages['invalid_id_div'] + this.config.id_div);
@@ -2672,17 +2848,38 @@ var Chessboard = (function () {
2672
2848
  }
2673
2849
 
2674
2850
  movePiece(piece, to, duration, callback) {
2851
+ if (!piece) {
2852
+ console.warn('movePiece: piece is null, skipping animation');
2853
+ if (callback) callback();
2854
+ return;
2855
+ }
2856
+
2675
2857
  piece.translate(to, duration, this.transitionTimingFunction, this.config.moveAnimation, callback);
2676
2858
  }
2677
2859
 
2678
2860
  translatePiece(move, removeTo, animate, callback = null) {
2861
+ if (!move.piece) {
2862
+ console.warn('translatePiece: move.piece is null, skipping translation');
2863
+ if (callback) callback();
2864
+ return;
2865
+ }
2679
2866
 
2680
- if (removeTo) this.removePieceFromSquare(move.to, false);
2867
+ if (removeTo) {
2868
+ // Deselect the captured piece before removing it
2869
+ move.to.deselect();
2870
+ this.removePieceFromSquare(move.to, false);
2871
+ }
2681
2872
 
2682
2873
  let change_square = () => {
2683
- move.from.removePiece();
2684
- move.to.putPiece(move.piece);
2685
- move.piece.setDrag(this.dragFunction(move.to, move.piece));
2874
+ // Check if piece still exists and is on the source square
2875
+ if (move.from.piece === move.piece) {
2876
+ move.from.removePiece();
2877
+ }
2878
+ // Only put piece if destination square doesn't already have it
2879
+ if (move.to.piece !== move.piece) {
2880
+ move.to.putPiece(move.piece);
2881
+ move.piece.setDrag(this.dragFunction(move.to, move.piece));
2882
+ }
2686
2883
  if (callback) callback();
2687
2884
  };
2688
2885
 
@@ -2693,14 +2890,49 @@ var Chessboard = (function () {
2693
2890
  }
2694
2891
 
2695
2892
  snapbackPiece(square, animate = this.config.snapbackAnimation) {
2696
- let move = new Move(square, square);
2697
- this.translatePiece(move, false, animate);
2893
+ if (!square || !square.piece) {
2894
+ return;
2895
+ }
2896
+
2897
+ const piece = square.piece;
2898
+
2899
+ // Use the piece's translate method to properly animate back to the square
2900
+ const duration = animate ? this.config.snapbackTime : 0;
2901
+
2902
+ // The translate method will calculate the proper distance from current visual position
2903
+ // back to the square's position
2904
+ piece.translate(square, duration, this.transitionTimingFunction, animate);
2698
2905
  }
2699
2906
 
2700
2907
  // -------------------
2701
2908
  // Board Update Functions
2702
2909
  // -------------------
2703
2910
  updateBoardPieces(animation = false) {
2911
+ // Clear any pending update to avoid duplicate calls
2912
+ if (this._updateTimeout) {
2913
+ clearTimeout(this._updateTimeout);
2914
+ this._updateTimeout = null;
2915
+ }
2916
+
2917
+ // Pulisce la cache delle mosse quando la posizione cambia
2918
+ this._movesCache.clear();
2919
+ if (this._cacheTimeout) {
2920
+ clearTimeout(this._cacheTimeout);
2921
+ this._cacheTimeout = null;
2922
+ }
2923
+
2924
+ // For click-to-move, add a small delay to avoid lag
2925
+ if (animation && this.clicked === null) {
2926
+ this._updateTimeout = setTimeout(() => {
2927
+ this._doUpdateBoardPieces(animation);
2928
+ this._updateTimeout = null;
2929
+ }, 10);
2930
+ } else {
2931
+ this._doUpdateBoardPieces(animation);
2932
+ }
2933
+ }
2934
+
2935
+ _doUpdateBoardPieces(animation = false) {
2704
2936
  let { updatedFlags, escapeFlags, movableFlags, pendingTranslations } = this.prepareBoardUpdateData();
2705
2937
 
2706
2938
  let change = Object.values(updatedFlags).some(flag => !flag);
@@ -2840,30 +3072,91 @@ var Chessboard = (function () {
2840
3072
 
2841
3073
  if (!this.config.draggable || !piece) return;
2842
3074
 
3075
+ // Store the original from square for the entire drag operation
3076
+ const originalFrom = square;
2843
3077
  let prec;
2844
- let from = square;
3078
+ let from = originalFrom;
2845
3079
  let to = square;
2846
3080
 
2847
3081
  const img = piece.element;
2848
3082
 
2849
3083
  if (!this.canMove(from)) return;
2850
- if (!this.config.clickable) this.clicked = null;
2851
- if (this.onClick(from, true, true)) return;
2852
-
2853
- img.style.position = 'absolute';
2854
- img.style.zIndex = 100;
2855
-
2856
- const moveAt = (pageX, pageY) => {
2857
- const halfWidth = img.offsetWidth / 2;
2858
- const halfHeight = img.offsetHeight / 2;
2859
- img.style.left = `${pageX - halfWidth}px`;
2860
- img.style.top = `${pageY - halfHeight}px`;
3084
+
3085
+ // Track if this is actually a drag operation or just a click
3086
+ let isDragging = false;
3087
+ let startX = event.clientX || (event.touches && event.touches[0] ? event.touches[0].clientX : 0);
3088
+ let startY = event.clientY || (event.touches && event.touches[0] ? event.touches[0].clientY : 0);
3089
+
3090
+ // Don't interfere with click system immediately
3091
+ console.log('dragFunction: mousedown detected, waiting to see if it becomes drag');
3092
+
3093
+ const moveAt = (event) => {
3094
+ const squareSize = this.element.offsetWidth / 8;
3095
+
3096
+ // Get mouse coordinates - use clientX/Y for better Chrome compatibility
3097
+ let clientX, clientY;
3098
+ if (event.touches && event.touches[0]) {
3099
+ clientX = event.touches[0].clientX;
3100
+ clientY = event.touches[0].clientY;
3101
+ } else {
3102
+ clientX = event.clientX;
3103
+ clientY = event.clientY;
3104
+ }
3105
+
3106
+ // Get board position using getBoundingClientRect for accuracy
3107
+ const boardRect = this.element.getBoundingClientRect();
3108
+
3109
+ // Calculate position relative to board with piece centered on cursor
3110
+ // Add window scroll offset for correct positioning
3111
+ window.pageXOffset || document.documentElement.scrollLeft;
3112
+ window.pageYOffset || document.documentElement.scrollTop;
3113
+
3114
+ const x = clientX - boardRect.left - (squareSize / 2);
3115
+ const y = clientY - boardRect.top - (squareSize / 2);
3116
+
3117
+ img.style.left = x + 'px';
3118
+ img.style.top = y + 'px';
2861
3119
  return true;
2862
3120
  };
2863
3121
 
2864
3122
  const onMouseMove = (event) => {
3123
+ // Check if mouse has moved enough to be considered a drag
3124
+ let currentX = event.clientX;
3125
+ let currentY = event.clientY;
3126
+ let deltaX = Math.abs(currentX - startX);
3127
+ let deltaY = Math.abs(currentY - startY);
3128
+
3129
+ // Only start dragging if mouse moved more than 3 pixels
3130
+ if (!isDragging && (deltaX > 3 || deltaY > 3)) {
3131
+ console.log('dragFunction: starting actual drag operation');
3132
+ isDragging = true;
3133
+
3134
+ // Now set up drag state
3135
+ if (!this.config.clickable) {
3136
+ this.clicked = null;
3137
+ this.clicked = from;
3138
+ } else if (!this.clicked) {
3139
+ this.clicked = from;
3140
+ }
3141
+ console.log('dragFunction: clicked state after drag activation =', this.clicked ? this.clicked.id : 'none');
3142
+
3143
+ // Highlight the source square and show hints
3144
+ if (this.config.clickable) {
3145
+ from.select();
3146
+ this.hintMoves(from);
3147
+ }
3148
+
3149
+ img.style.position = 'absolute';
3150
+ img.style.zIndex = 100;
3151
+ img.classList.add('dragging');
3152
+
3153
+ DragOptimizations.enableForDrag(img);
3154
+ }
3155
+
3156
+ if (!isDragging) return;
3157
+
2865
3158
  if (!this.config.onDragStart(square, piece)) return;
2866
- if (!moveAt(event.pageX, event.pageY)) ;
3159
+ if (!moveAt(event)) ;
2867
3160
 
2868
3161
  const boardRect = this.element.getBoundingClientRect();
2869
3162
  const { offsetWidth: boardWidth, offsetHeight: boardHeight } = this.element;
@@ -2891,19 +3184,44 @@ var Chessboard = (function () {
2891
3184
  prec?.dehighlight();
2892
3185
  document.removeEventListener('mousemove', onMouseMove);
2893
3186
  window.removeEventListener('mouseup', onMouseUp);
3187
+
3188
+ // If this was just a click (not a drag), don't interfere
3189
+ if (!isDragging) {
3190
+ console.log('dragFunction: was just a click, not interfering');
3191
+ return;
3192
+ }
3193
+
3194
+ console.log('dragFunction: ending drag operation');
2894
3195
  img.style.zIndex = 20;
3196
+ img.classList.remove('dragging');
3197
+ img.style.willChange = 'auto';
2895
3198
 
2896
- const dropResult = this.config.onDrop(from, to, piece);
3199
+ const dropResult = this.config.onDrop(originalFrom, to, piece);
2897
3200
  const isTrashDrop = !to && (this.config.dropOffBoard === 'trash' || dropResult === 'trash');
2898
3201
 
2899
3202
  if (isTrashDrop) {
2900
3203
  this.allSquares("unmoved");
2901
3204
  this.allSquares('removeHint');
2902
- from.deselect();
2903
- this.remove(from);
2904
- } else if (!to || !this.onClick(to, true, true)) {
2905
- this.snapbackPiece(from);
2906
- if (to !== from) this.config.onSnapbackEnd(from, piece);
3205
+ originalFrom.deselect();
3206
+ this.remove(originalFrom);
3207
+ } else if (!to) {
3208
+ // No target square - snapback
3209
+ if (originalFrom && originalFrom.piece) {
3210
+ this.snapbackPiece(originalFrom);
3211
+ if (to !== originalFrom) this.config.onSnapbackEnd(originalFrom, piece);
3212
+ }
3213
+ } else {
3214
+ // Set clicked to originalFrom before attempting move
3215
+ this.clicked = originalFrom;
3216
+ // Try to make the move
3217
+ const onClickResult = this.onClick(to, true, true);
3218
+ if (!onClickResult) {
3219
+ // Move failed - snapback
3220
+ if (originalFrom && originalFrom.piece) {
3221
+ this.snapbackPiece(originalFrom);
3222
+ if (to !== originalFrom) this.config.onSnapbackEnd(originalFrom, piece);
3223
+ }
3224
+ }
2907
3225
  }
2908
3226
  };
2909
3227
 
@@ -2916,32 +3234,38 @@ var Chessboard = (function () {
2916
3234
  addListeners() {
2917
3235
  for (const square of Object.values(this.squares)) {
2918
3236
 
2919
- let piece = square.piece;
3237
+ square.piece;
2920
3238
 
2921
- square.element.addEventListener("mouseover", (e) => {
2922
- if (!this.clicked) this.hintMoves(square);
3239
+ // Applica throttling ai listener di mouseover e mouseout per migliori prestazioni
3240
+ const throttledHintMoves = rafThrottle((e) => {
3241
+ if (!this.clicked && this.config.hints) this.hintMoves(square);
2923
3242
  });
2924
- square.element.addEventListener("mouseout", (e) => {
2925
- if (!this.clicked) this.dehintMoves(square);
3243
+
3244
+ const throttledDehintMoves = rafThrottle((e) => {
3245
+ if (!this.clicked && this.config.hints) this.dehintMoves(square);
2926
3246
  });
2927
3247
 
3248
+ square.element.addEventListener("mouseover", throttledHintMoves);
3249
+ square.element.addEventListener("mouseout", throttledDehintMoves);
3250
+
2928
3251
  const handleClick = (e) => {
2929
3252
  e.stopPropagation();
2930
- if (this.config.clickable && (!piece || this.config.onlyLegalMoves)) this.onClick(square);
3253
+ if (this.config.clickable) {
3254
+ this.onClick(square);
3255
+ }
2931
3256
  };
2932
3257
 
2933
- square.element.addEventListener("mousedown", handleClick);
3258
+ square.element.addEventListener("click", handleClick);
2934
3259
  square.element.addEventListener("touch", handleClick);
2935
3260
  }
2936
3261
  }
2937
3262
 
2938
3263
  onClick(square, animation = this.config.moveAnimation, dragged = false) {
2939
3264
 
2940
- if (this.clicked === square) return false;
2941
-
3265
+ console.log('onClick START: square =', square.id, 'clicked =', this.clicked ? this.clicked.id : 'none');
3266
+
2942
3267
  let from = this.clicked;
2943
- this.clicked = null;
2944
-
3268
+
2945
3269
  let promotion = null;
2946
3270
 
2947
3271
  if (this.promoting) {
@@ -2953,36 +3277,75 @@ var Chessboard = (function () {
2953
3277
  this.allSquares("removeCover");
2954
3278
  }
2955
3279
 
2956
- if (!from) {
3280
+ console.log('onClick: from =', from ? from.id : 'none');
2957
3281
 
3282
+ if (!from) {
3283
+ console.log('onClick: no from, trying to select piece');
2958
3284
  if (this.canMove(square)) {
3285
+ console.log('onClick: canMove = true, selecting');
2959
3286
  if (this.config.clickable) {
2960
3287
  square.select();
2961
3288
  this.hintMoves(square);
2962
3289
  }
2963
3290
  this.clicked = square;
3291
+ console.log('onClick: *** CLICKED SET TO ***', square.id);
3292
+ console.log('onClick: set clicked to', square.id);
3293
+ } else {
3294
+ console.log('onClick: canMove = false');
2964
3295
  }
3296
+ return false;
3297
+ }
2965
3298
 
3299
+ // If clicking on the same square that's already selected, deselect it
3300
+ if (this.clicked === square) {
3301
+ console.log('onClick: deselecting same square');
3302
+ square.deselect();
3303
+ this.allSquares("removeHint");
3304
+ this.clicked = null;
3305
+ console.log('onClick: *** CLICKED RESET TO NULL (deselect) ***');
2966
3306
  return false;
2967
3307
  }
2968
3308
 
3309
+ console.log('onClick: attempting move from', from.id, 'to', square.id);
2969
3310
  let move = new Move(from, square, promotion);
2970
3311
 
2971
- move.from.deselect();
2972
-
2973
- if (!move.check()) return false;
2974
-
2975
- this.allSquares("removeHint");
3312
+ if (!move.check()) {
3313
+ console.log('onClick: move check FAILED');
3314
+ from.deselect();
3315
+ this.allSquares("removeHint");
3316
+ this.clicked = null;
3317
+ return false;
3318
+ }
2976
3319
 
2977
- if (this.config.onlyLegalMoves && !move.isLegal(this.game)) return false;
3320
+ if (this.config.onlyLegalMoves && !move.isLegal(this.game)) {
3321
+ console.log('onClick: move is NOT LEGAL');
3322
+ from.deselect();
3323
+ this.allSquares("removeHint");
3324
+ this.clicked = null;
3325
+ return false;
3326
+ }
2978
3327
 
2979
- if (!move.hasPromotion() && this.promote(move)) return false;
3328
+ if (!move.hasPromotion() && this.promote(move)) {
3329
+ console.log('onClick: promotion required');
3330
+ return false;
3331
+ }
2980
3332
 
3333
+ console.log('onClick: calling onMove');
2981
3334
  if (this.config.onMove(move)) {
3335
+ console.log('onClick: SUCCESS - move executed');
3336
+ // Clean up UI state
3337
+ from.deselect();
3338
+ this.allSquares("removeHint");
3339
+ this.clicked = null;
3340
+ console.log('onClick: *** CLICKED RESET TO NULL (success) ***');
2982
3341
  this.move(move, animation);
2983
3342
  return true;
2984
3343
  }
2985
3344
 
3345
+ console.log('onClick: onMove returned FALSE');
3346
+ from.deselect();
3347
+ this.allSquares("removeHint");
3348
+ this.clicked = null;
2986
3349
  return false;
2987
3350
  }
2988
3351
 
@@ -3033,12 +3396,14 @@ var Chessboard = (function () {
3033
3396
  let from = move.from;
3034
3397
  let to = move.to;
3035
3398
 
3399
+ // Store the current state to avoid unnecessary recalculations
3400
+ const gameStateBefore = this.game.fen();
3401
+
3036
3402
  if (!this.config.onlyLegalMoves) {
3037
3403
  let piece = this.getGamePieceId(from.id);
3038
3404
  this.game.remove(from.id);
3039
3405
  this.game.remove(to.id);
3040
3406
  this.game.put({ type: move.hasPromotion() ? move.promotion : piece[0], color: piece[1] }, to.id);
3041
- this.updateBoardPieces(animation);
3042
3407
  } else {
3043
3408
  this.allSquares("unmoved");
3044
3409
 
@@ -3052,12 +3417,18 @@ var Chessboard = (function () {
3052
3417
  throw new Error("Invalid move: move could not be executed");
3053
3418
  }
3054
3419
 
3055
- this.updateBoardPieces(animation);
3056
-
3057
3420
  from.moved();
3058
3421
  to.moved();
3059
3422
  this.allSquares("removeHint");
3423
+ }
3060
3424
 
3425
+ // Only update the board if the game state actually changed
3426
+ const gameStateAfter = this.game.fen();
3427
+ if (gameStateBefore !== gameStateAfter) {
3428
+ this.updateBoardPieces(animation);
3429
+ }
3430
+
3431
+ if (this.config.onlyLegalMoves) {
3061
3432
  this.config.onMoveEnd(move);
3062
3433
  }
3063
3434
  }
@@ -3073,14 +3444,37 @@ var Chessboard = (function () {
3073
3444
 
3074
3445
  hintMoves(square) {
3075
3446
  if (!this.canMove(square)) return;
3076
- let mosse = this.game.moves({ square: square.id, verbose: true });
3447
+
3448
+ // Usa la cache per evitare calcoli ripetuti delle mosse
3449
+ const cacheKey = `${square.id}-${this.game.fen()}`;
3450
+ let mosse = this._movesCache.get(cacheKey);
3451
+
3452
+ if (!mosse) {
3453
+ mosse = this.game.moves({ square: square.id, verbose: true });
3454
+ this._movesCache.set(cacheKey, mosse);
3455
+
3456
+ // Pulisci la cache dopo un breve ritardo per evitare accumulo di memoria
3457
+ if (this._cacheTimeout) clearTimeout(this._cacheTimeout);
3458
+ this._cacheTimeout = setTimeout(() => {
3459
+ this._movesCache.clear();
3460
+ }, 1000);
3461
+ }
3462
+
3077
3463
  for (let mossa of mosse) {
3078
3464
  if (mossa['to'].length === 2) this.hint(mossa['to']);
3079
3465
  }
3080
3466
  }
3081
3467
 
3082
3468
  dehintMoves(square) {
3083
- let mosse = this.game.moves({ square: square.id, verbose: true });
3469
+ // Usa la cache anche per dehint per coerenza
3470
+ const cacheKey = `${square.id}-${this.game.fen()}`;
3471
+ let mosse = this._movesCache.get(cacheKey);
3472
+
3473
+ if (!mosse) {
3474
+ mosse = this.game.moves({ square: square.id, verbose: true });
3475
+ this._movesCache.set(cacheKey, mosse);
3476
+ }
3477
+
3084
3478
  for (let mossa of mosse) {
3085
3479
  let to = this.squares[mossa['to']];
3086
3480
  to.removeHint();
@@ -3413,10 +3807,266 @@ var Chessboard = (function () {
3413
3807
 
3414
3808
  isWhiteOriented() { return this.config.orientation === 'w' }
3415
3809
 
3810
+ };
3811
+
3812
+ /**
3813
+ * Chessboard.js - A beautiful, customizable chessboard widget
3814
+ * Entry point for the core library
3815
+ */
3816
+
3817
+
3818
+ // Factory function to maintain backward compatibility
3819
+ function Chessboard(containerElm, config = {}) {
3820
+ // If first parameter is an object, treat it as config
3821
+ if (typeof containerElm === 'object' && containerElm !== null) {
3822
+ return new Chessboard$1(containerElm);
3823
+ }
3824
+
3825
+ // Otherwise, treat first parameter as element ID
3826
+ const fullConfig = { ...config, id: containerElm };
3827
+ return new Chessboard$1(fullConfig);
3828
+ }
3829
+
3830
+ // Wrapper class that handles both calling conventions
3831
+ class ChessboardWrapper extends Chessboard$1 {
3832
+ constructor(containerElm, config = {}) {
3833
+ // If first parameter is an object, treat it as config
3834
+ if (typeof containerElm === 'object' && containerElm !== null) {
3835
+ super(containerElm);
3836
+ } else {
3837
+ // Otherwise, treat first parameter as element ID
3838
+ const fullConfig = { ...config, id: containerElm };
3839
+ super(fullConfig);
3840
+ }
3841
+ }
3842
+ }
3843
+
3844
+ // Attach the class to the factory function for direct access
3845
+ Chessboard.Class = ChessboardWrapper;
3846
+ Chessboard.Chessboard = ChessboardWrapper;
3847
+
3848
+ /**
3849
+ * Coordinate utilities for Chessboard.js
3850
+ */
3851
+
3852
+ /**
3853
+ * Convert algebraic notation to array coordinates
3854
+ * @param {string} square - Square in algebraic notation (e.g., 'a1', 'h8')
3855
+ * @returns {Object} Object with row and col properties
3856
+ */
3857
+ function algebraicToCoords(square) {
3858
+ const file = square.charCodeAt(0) - 97; // 'a' = 0, 'b' = 1, etc.
3859
+ const rank = parseInt(square[1]) - 1; // '1' = 0, '2' = 1, etc.
3860
+
3861
+ return { row: 7 - rank, col: file };
3862
+ }
3863
+
3864
+ /**
3865
+ * Convert array coordinates to algebraic notation
3866
+ * @param {number} row - Row index (0-7)
3867
+ * @param {number} col - Column index (0-7)
3868
+ * @returns {string} Square in algebraic notation
3869
+ */
3870
+ function coordsToAlgebraic(row, col) {
3871
+ const file = String.fromCharCode(97 + col); // 0 = 'a', 1 = 'b', etc.
3872
+ const rank = (8 - row).toString(); // 0 = '8', 1 = '7', etc.
3873
+
3874
+ return file + rank;
3875
+ }
3876
+
3877
+ /**
3878
+ * Get the color of a square
3879
+ * @param {string} square - Square in algebraic notation
3880
+ * @returns {string} 'light' or 'dark'
3881
+ */
3882
+ function getSquareColor(square) {
3883
+ const { row, col } = algebraicToCoords(square);
3884
+ return (row + col) % 2 === 0 ? 'dark' : 'light';
3885
+ }
3886
+
3887
+ /**
3888
+ * Check if coordinates are valid
3889
+ * @param {number} row - Row index
3890
+ * @param {number} col - Column index
3891
+ * @returns {boolean} True if coordinates are valid
3892
+ */
3893
+ function isValidCoords(row, col) {
3894
+ return row >= 0 && row <= 7 && col >= 0 && col <= 7;
3895
+ }
3896
+
3897
+ /**
3898
+ * Check if algebraic notation is valid
3899
+ * @param {string} square - Square in algebraic notation
3900
+ * @returns {boolean} True if square notation is valid
3901
+ */
3902
+ function isValidSquare$1(square) {
3903
+ if (typeof square !== 'string' || square.length !== 2) return false;
3904
+
3905
+ const file = square[0];
3906
+ const rank = square[1];
3907
+
3908
+ return file >= 'a' && file <= 'h' && rank >= '1' && rank <= '8';
3909
+ }
3910
+
3911
+ /**
3912
+ * Validation utilities for Chessboard.js
3913
+ */
3914
+
3915
+ /**
3916
+ * Validate piece notation
3917
+ * @param {string} piece - Piece notation (e.g., 'wP', 'bK')
3918
+ * @returns {boolean} True if piece notation is valid
3919
+ */
3920
+ function isValidPiece(piece) {
3921
+ if (typeof piece !== 'string' || piece.length !== 2) return false;
3922
+
3923
+ const color = piece[0];
3924
+ const type = piece[1];
3925
+
3926
+ return ['w', 'b'].includes(color) && ['P', 'R', 'N', 'B', 'Q', 'K'].includes(type);
3927
+ }
3928
+
3929
+ /**
3930
+ * Validate position object
3931
+ * @param {Object} position - Position object with square-piece mappings
3932
+ * @returns {boolean} True if position is valid
3933
+ */
3934
+ function isValidPosition(position) {
3935
+ if (typeof position !== 'object' || position === null) return false;
3936
+
3937
+ for (const [square, piece] of Object.entries(position)) {
3938
+ if (!isValidSquare(square) || !isValidPiece(piece)) {
3939
+ return false;
3940
+ }
3941
+ }
3942
+
3943
+ return true;
3944
+ }
3945
+
3946
+ /**
3947
+ * Validate FEN string format
3948
+ * @param {string} fen - FEN string
3949
+ * @returns {Object} Validation result with success and error properties
3950
+ */
3951
+ function validateFenFormat(fen) {
3952
+ if (typeof fen !== 'string') {
3953
+ return { success: false, error: 'FEN must be a string' };
3954
+ }
3955
+
3956
+ const parts = fen.split(' ');
3957
+ if (parts.length !== 6) {
3958
+ return { success: false, error: 'FEN must have 6 parts separated by spaces' };
3959
+ }
3960
+
3961
+ // Validate piece placement
3962
+ const ranks = parts[0].split('/');
3963
+ if (ranks.length !== 8) {
3964
+ return { success: false, error: 'Piece placement must have 8 ranks' };
3965
+ }
3966
+
3967
+ return { success: true };
3968
+ }
3969
+
3970
+ /**
3971
+ * Validate configuration object
3972
+ * @param {Object} config - Configuration object
3973
+ * @returns {Object} Validation result with success and errors array
3974
+ */
3975
+ function validateConfig(config) {
3976
+ const errors = [];
3977
+
3978
+ if (config.orientation && !['white', 'black', 'w', 'b'].includes(config.orientation)) {
3979
+ errors.push('Invalid orientation. Must be "white", "black", "w", or "b"');
3980
+ }
3981
+
3982
+ if (config.position && config.position !== 'start' && typeof config.position !== 'object') {
3983
+ errors.push('Invalid position. Must be "start" or a position object');
3984
+ }
3985
+
3986
+ if (config.size && typeof config.size !== 'string' && typeof config.size !== 'number') {
3987
+ errors.push('Invalid size. Must be a string or number');
3988
+ }
3989
+
3990
+ return {
3991
+ success: errors.length === 0,
3992
+ errors
3993
+ };
3416
3994
  }
3417
3995
 
3418
- return Chessboard;
3996
+ /**
3997
+ * Animation utilities for Chessboard.js
3998
+ */
3419
3999
 
3420
- })();
4000
+ /**
4001
+ * Get the CSS transition duration in milliseconds
4002
+ * @param {string|number} time - Time value ('fast', 'slow', or number in ms)
4003
+ * @returns {number} Duration in milliseconds
4004
+ */
4005
+ function parseTime(time) {
4006
+ if (typeof time === 'number') return time;
4007
+
4008
+ switch (time) {
4009
+ case 'fast': return 150;
4010
+ case 'slow': return 500;
4011
+ default: return 200;
4012
+ }
4013
+ }
4014
+
4015
+ /**
4016
+ * Get the CSS transition function
4017
+ * @param {string} animation - Animation type ('ease', 'linear', etc.)
4018
+ * @returns {string} CSS transition function
4019
+ */
4020
+ function parseAnimation(animation) {
4021
+ const validAnimations = ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear'];
4022
+ return validAnimations.includes(animation) ? animation : 'ease';
4023
+ }
4024
+
4025
+ /**
4026
+ * Create a promise that resolves after animation completion
4027
+ * @param {number} duration - Duration in milliseconds
4028
+ * @returns {Promise} Promise that resolves after the duration
4029
+ */
4030
+ function animationPromise(duration) {
4031
+ return new Promise(resolve => setTimeout(resolve, duration));
4032
+ }
4033
+
4034
+ /**
4035
+ * Chessboard.js - A beautiful, customizable chessboard widget
4036
+ * Main entry point for the library
4037
+ *
4038
+ * @version 2.2.1
4039
+ * @author alepot55
4040
+ * @license ISC
4041
+ */
3421
4042
 
3422
- window.Chessboard.Chessboard = Chessboard;
4043
+ exports.Chess = Chess;
4044
+ exports.Chessboard = Chessboard;
4045
+ exports.ChessboardConfig = ChessboardConfig;
4046
+ exports.Move = Move;
4047
+ exports.Piece = Piece;
4048
+ exports.Square = Square;
4049
+ exports.algebraicToCoords = algebraicToCoords;
4050
+ exports.animationPromise = animationPromise;
4051
+ exports.coordsToAlgebraic = coordsToAlgebraic;
4052
+ exports.default = Chessboard;
4053
+ exports.getSquareColor = getSquareColor;
4054
+ exports.isValidCoords = isValidCoords;
4055
+ exports.isValidPiece = isValidPiece;
4056
+ exports.isValidPosition = isValidPosition;
4057
+ exports.isValidSquare = isValidSquare$1;
4058
+ exports.parseAnimation = parseAnimation;
4059
+ exports.parseTime = parseTime;
4060
+ exports.rafThrottle = rafThrottle;
4061
+ exports.resetTransform = resetTransform;
4062
+ exports.setTransform = setTransform;
4063
+ exports.throttle = throttle;
4064
+ exports.validateConfig = validateConfig;
4065
+ exports.validateFen = validateFen;
4066
+ exports.validateFenFormat = validateFenFormat;
4067
+
4068
+ Object.defineProperty(exports, '__esModule', { value: true });
4069
+
4070
+ return exports;
4071
+
4072
+ })({});