@alepot55/chessboardjs 2.2.1 → 2.3.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 (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 +10476 -0
  10. package/dist/chessboard.css +197 -0
  11. package/dist/chessboard.esm.js +10407 -0
  12. package/dist/chessboard.iife.js +10481 -0
  13. package/dist/chessboard.umd.js +10482 -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 +1930 -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,189 @@
1
+ /**
2
+ * Error messages and error handling utilities
3
+ * @module errors/messages
4
+ * @since 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Error codes for categorizing different types of errors
9
+ * @type {Object.<string, string>}
10
+ * @readonly
11
+ */
12
+ export const ERROR_CODES = Object.freeze({
13
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
14
+ CONFIG_ERROR: 'CONFIG_ERROR',
15
+ MOVE_ERROR: 'MOVE_ERROR',
16
+ DOM_ERROR: 'DOM_ERROR',
17
+ ANIMATION_ERROR: 'ANIMATION_ERROR',
18
+ PIECE_ERROR: 'PIECE_ERROR',
19
+ INITIALIZATION_ERROR: 'INITIALIZATION_ERROR',
20
+ POSITION_ERROR: 'POSITION_ERROR',
21
+ FEN_ERROR: 'FEN_ERROR',
22
+ SQUARE_ERROR: 'SQUARE_ERROR',
23
+ THEME_ERROR: 'THEME_ERROR',
24
+ NETWORK_ERROR: 'NETWORK_ERROR'
25
+ });
26
+
27
+ /**
28
+ * Standardized error messages for the chessboard application
29
+ * @type {Object.<string, string>}
30
+ * @readonly
31
+ */
32
+ export const ERROR_MESSAGES = Object.freeze({
33
+ // Position and FEN related
34
+ invalid_position: 'Invalid position: ',
35
+ invalid_fen: 'Invalid FEN string: ',
36
+ position_load_failed: 'Failed to load position: ',
37
+
38
+ // DOM and UI related
39
+ invalid_id_div: 'Board container not found: ',
40
+ invalid_value: 'Invalid value: ',
41
+ dom_operation_failed: 'DOM operation failed: ',
42
+ element_not_found: 'Element not found: ',
43
+
44
+ // Piece related
45
+ invalid_piece: 'Invalid piece notation: ',
46
+ invalid_square: 'Invalid square notation: ',
47
+ invalid_piecesPath: 'Invalid pieces path: ',
48
+ piece_operation_failed: 'Piece operation failed: ',
49
+
50
+ // Board configuration
51
+ invalid_orientation: 'Invalid orientation: ',
52
+ invalid_color: 'Invalid color: ',
53
+ invalid_mode: 'Invalid mode: ',
54
+ invalid_dropOffBoard: 'Invalid dropOffBoard setting: ',
55
+ invalid_size: 'Invalid size: ',
56
+ invalid_movableColors: 'Invalid movableColors setting: ',
57
+
58
+ // Animation related
59
+ invalid_snapbackTime: 'Invalid snapbackTime: ',
60
+ invalid_snapbackAnimation: 'Invalid snapbackAnimation: ',
61
+ invalid_fadeTime: 'Invalid fadeTime: ',
62
+ invalid_fadeAnimation: 'Invalid fadeAnimation: ',
63
+ invalid_ratio: 'Invalid ratio: ',
64
+ invalid_animationStyle: 'Invalid animationStyle: ',
65
+ animation_failed: 'Animation failed: ',
66
+
67
+ // Event handlers
68
+ invalid_onMove: 'Invalid onMove callback: ',
69
+ invalid_onMoveEnd: 'Invalid onMoveEnd callback: ',
70
+ invalid_onChange: 'Invalid onChange callback: ',
71
+ invalid_onDragStart: 'Invalid onDragStart callback: ',
72
+ invalid_onDragMove: 'Invalid onDragMove callback: ',
73
+ invalid_onDrop: 'Invalid onDrop callback: ',
74
+ invalid_onSnapbackEnd: 'Invalid onSnapbackEnd callback: ',
75
+ callback_execution_failed: 'Callback execution failed: ',
76
+
77
+ // Visual styling
78
+ invalid_whiteSquare: 'Invalid whiteSquare color: ',
79
+ invalid_blackSquare: 'Invalid blackSquare color: ',
80
+ invalid_highlight: 'Invalid highlight color: ',
81
+ invalid_selectedSquareWhite: 'Invalid selectedSquareWhite color: ',
82
+ invalid_selectedSquareBlack: 'Invalid selectedSquareBlack color: ',
83
+ invalid_movedSquareWhite: 'Invalid movedSquareWhite color: ',
84
+ invalid_movedSquareBlack: 'Invalid movedSquareBlack color: ',
85
+ invalid_choiceSquare: 'Invalid choiceSquare color: ',
86
+ invalid_coverSquare: 'Invalid coverSquare color: ',
87
+ invalid_hintColor: 'Invalid hintColor: ',
88
+
89
+ // Move related
90
+ invalid_move: 'Invalid move: ',
91
+ invalid_move_format: 'Invalid move format: ',
92
+ move_execution_failed: 'Move execution failed: ',
93
+ illegal_move: 'Illegal move: ',
94
+ square_no_piece: 'No piece found on square: ',
95
+ move_validation_failed: 'Move validation failed: ',
96
+
97
+ // Game state
98
+ game_over: 'Game is over',
99
+ invalid_turn: 'Invalid turn: ',
100
+ position_validation_failed: 'Position validation failed: ',
101
+
102
+ // Theme and assets
103
+ theme_load_failed: 'Theme loading failed: ',
104
+ asset_load_failed: 'Asset loading failed: ',
105
+ invalid_theme: 'Invalid theme: ',
106
+
107
+ // Network and resources
108
+ network_error: 'Network error: ',
109
+ resource_not_found: 'Resource not found: ',
110
+ timeout_error: 'Operation timed out: ',
111
+
112
+ // General errors
113
+ initialization_failed: 'Initialization failed: ',
114
+ operation_failed: 'Operation failed: ',
115
+ invalid_state: 'Invalid state: ',
116
+ memory_limit_exceeded: 'Memory limit exceeded',
117
+ performance_degraded: 'Performance degraded: '
118
+ });
119
+
120
+ /**
121
+ * Error severity levels
122
+ * @type {Object.<string, string>}
123
+ * @readonly
124
+ */
125
+ export const ERROR_SEVERITY = Object.freeze({
126
+ LOW: 'LOW',
127
+ MEDIUM: 'MEDIUM',
128
+ HIGH: 'HIGH',
129
+ CRITICAL: 'CRITICAL'
130
+ });
131
+
132
+ /**
133
+ * Maps error codes to their severity levels
134
+ * @type {Object.<string, string>}
135
+ * @readonly
136
+ */
137
+ export const ERROR_SEVERITY_MAP = Object.freeze({
138
+ [ERROR_CODES.VALIDATION_ERROR]: ERROR_SEVERITY.MEDIUM,
139
+ [ERROR_CODES.CONFIG_ERROR]: ERROR_SEVERITY.HIGH,
140
+ [ERROR_CODES.MOVE_ERROR]: ERROR_SEVERITY.LOW,
141
+ [ERROR_CODES.DOM_ERROR]: ERROR_SEVERITY.HIGH,
142
+ [ERROR_CODES.ANIMATION_ERROR]: ERROR_SEVERITY.LOW,
143
+ [ERROR_CODES.PIECE_ERROR]: ERROR_SEVERITY.MEDIUM,
144
+ [ERROR_CODES.INITIALIZATION_ERROR]: ERROR_SEVERITY.CRITICAL,
145
+ [ERROR_CODES.POSITION_ERROR]: ERROR_SEVERITY.MEDIUM,
146
+ [ERROR_CODES.FEN_ERROR]: ERROR_SEVERITY.MEDIUM,
147
+ [ERROR_CODES.SQUARE_ERROR]: ERROR_SEVERITY.MEDIUM,
148
+ [ERROR_CODES.THEME_ERROR]: ERROR_SEVERITY.LOW,
149
+ [ERROR_CODES.NETWORK_ERROR]: ERROR_SEVERITY.MEDIUM
150
+ });
151
+
152
+ /**
153
+ * Utility function to get error severity
154
+ * @param {string} errorCode - Error code
155
+ * @returns {string} Error severity level
156
+ */
157
+ export function getErrorSeverity(errorCode) {
158
+ return ERROR_SEVERITY_MAP[errorCode] || ERROR_SEVERITY.MEDIUM;
159
+ }
160
+
161
+ /**
162
+ * Utility function to format error message
163
+ * @param {string} messageKey - Message key from ERROR_MESSAGES
164
+ * @param {string} [details] - Additional details
165
+ * @returns {string} Formatted error message
166
+ */
167
+ export function formatErrorMessage(messageKey, details = '') {
168
+ const message = ERROR_MESSAGES[messageKey];
169
+ if (!message) {
170
+ return `Unknown error: ${messageKey}${details ? ` - ${details}` : ''}`;
171
+ }
172
+ return `${message}${details}`;
173
+ }
174
+
175
+ /**
176
+ * Utility function to create error context
177
+ * @param {string} operation - Operation that failed
178
+ * @param {Object} [data] - Additional context data
179
+ * @returns {Object} Error context object
180
+ */
181
+ export function createErrorContext(operation, data = {}) {
182
+ return {
183
+ operation,
184
+ timestamp: new Date().toISOString(),
185
+ userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Unknown',
186
+ url: typeof window !== 'undefined' ? window.location.href : 'Unknown',
187
+ ...data
188
+ };
189
+ }
package/src/index.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Chessboard.js - A beautiful, customizable chessboard widget
3
+ * Main entry point for the library
4
+ *
5
+ * @version 2.2.1
6
+ * @author alepot55
7
+ * @license ISC
8
+ */
9
+
10
+ // Core components
11
+ export {
12
+ Chessboard,
13
+ ChessboardConfig,
14
+ ChessboardFactory,
15
+ chessboardFactory,
16
+ createChessboard,
17
+ createChessboardFromTemplate
18
+ } from './core/index.js';
19
+
20
+ // Component exports
21
+ export { default as Square } from './components/Square.js';
22
+ export { default as Piece } from './components/Piece.js';
23
+ export { default as Move } from './components/Move.js';
24
+
25
+ // Service exports for advanced usage
26
+ export {
27
+ AnimationService,
28
+ BoardService,
29
+ CoordinateService,
30
+ EventService,
31
+ MoveService,
32
+ PieceService,
33
+ PositionService,
34
+ ValidationService
35
+ } from './services/index.js';
36
+
37
+ // Constants exports
38
+ export * from './constants/index.js';
39
+
40
+ // Error handling exports
41
+ export {
42
+ ChessboardError,
43
+ ValidationError,
44
+ ConfigurationError,
45
+ MoveError,
46
+ DOMError,
47
+ PieceError
48
+ } from './errors/index.js';
49
+
50
+ // Utility exports
51
+ export { Chess, validateFen } from './utils/chess.js';
52
+ export {
53
+ PerformanceMonitor,
54
+ throttle,
55
+ debounce,
56
+ rafThrottle,
57
+ setTransform,
58
+ resetTransform,
59
+ batchDOMOperations,
60
+ isElementVisible,
61
+ getMemoryUsage
62
+ } from './utils/performance.js';
63
+ export * from './utils/coordinates.js';
64
+ export {
65
+ isValidPiece,
66
+ isValidPosition,
67
+ validateFenFormat,
68
+ validateMove,
69
+ validateConfig,
70
+ batchValidate,
71
+ clearValidationCache,
72
+ getValidationCacheStats
73
+ } from './utils/validation.js';
74
+ export * from './utils/animations.js';
75
+
76
+ // Logging system exports
77
+ export {
78
+ Logger,
79
+ logger,
80
+ createLogger,
81
+ LOG_LEVELS
82
+ } from './utils/logger.js';
83
+
84
+ // Default export for IIFE compatibility
85
+ import { Chessboard as ChessboardClass } from './core/index.js';
86
+ export default ChessboardClass;
87
+
88
+ // Version information
89
+ export const version = '2.2.1';
90
+ export const build = process.env.BUILD_NUMBER || 'dev';
91
+ export const buildDate = process.env.BUILD_DATE || new Date().toISOString();
92
+
93
+ // Library information
94
+ export const info = {
95
+ name: 'Chessboard.js',
96
+ version,
97
+ build,
98
+ buildDate,
99
+ author: 'alepot55',
100
+ license: 'ISC',
101
+ repository: 'https://github.com/alepot55/Chessboardjs',
102
+ homepage: 'https://chessboardjs.com'
103
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Service for managing animations and transitions
3
+ * @module services/AnimationService
4
+ * @since 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Service responsible for coordinating animations and transitions
9
+ * @class
10
+ */
11
+ export class AnimationService {
12
+ /**
13
+ * Creates a new AnimationService instance
14
+ * @param {ChessboardConfig} config - Board configuration
15
+ */
16
+ constructor(config) {
17
+ this.config = config;
18
+ this.activeAnimations = new Map();
19
+ this.animationId = 0;
20
+ }
21
+
22
+ /**
23
+ * Creates a timing function for animations
24
+ * @param {string} [type='ease'] - Animation type
25
+ * @returns {Function} Timing function
26
+ */
27
+ createTimingFunction(type = 'ease') {
28
+ return (elapsed, duration) => {
29
+ const x = elapsed / duration;
30
+
31
+ switch (type) {
32
+ case 'linear':
33
+ return x;
34
+ case 'ease':
35
+ return (x ** 2) * (3 - 2 * x);
36
+ case 'ease-in':
37
+ return x ** 2;
38
+ case 'ease-out':
39
+ return -1 * (x - 1) ** 2 + 1;
40
+ case 'ease-in-out':
41
+ return (x < 0.5) ? 2 * x ** 2 : 4 * x - 2 * x ** 2 - 1;
42
+ default:
43
+ return x;
44
+ }
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Animates an element with given properties
50
+ * @param {HTMLElement} element - Element to animate
51
+ * @param {Object} properties - Properties to animate
52
+ * @param {number} duration - Animation duration in milliseconds
53
+ * @param {string} [easing='ease'] - Easing function
54
+ * @param {Function} [callback] - Callback when animation completes
55
+ * @returns {number} Animation ID
56
+ */
57
+ animate(element, properties, duration, easing = 'ease', callback) {
58
+ const animationId = ++this.animationId;
59
+ const timingFunction = this.createTimingFunction(easing);
60
+ const startTime = performance.now();
61
+ const startValues = {};
62
+
63
+ // Store initial values
64
+ Object.keys(properties).forEach(prop => {
65
+ startValues[prop] = this._getInitialValue(element, prop);
66
+ });
67
+
68
+ const animate = (currentTime) => {
69
+ const elapsed = currentTime - startTime;
70
+ const progress = Math.min(elapsed / duration, 1);
71
+ const easedProgress = timingFunction(elapsed, duration);
72
+
73
+ // Apply animated values
74
+ Object.keys(properties).forEach(prop => {
75
+ const startValue = startValues[prop];
76
+ const endValue = properties[prop];
77
+ const currentValue = this._interpolateValue(startValue, endValue, easedProgress);
78
+ this._applyValue(element, prop, currentValue);
79
+ });
80
+
81
+ if (progress < 1 && this.activeAnimations.has(animationId)) {
82
+ requestAnimationFrame(animate);
83
+ } else {
84
+ this.activeAnimations.delete(animationId);
85
+ if (callback) callback();
86
+ }
87
+ };
88
+
89
+ this.activeAnimations.set(animationId, { element, animate, callback });
90
+ requestAnimationFrame(animate);
91
+
92
+ return animationId;
93
+ }
94
+
95
+ /**
96
+ * Cancels an animation
97
+ * @param {number} animationId - Animation ID to cancel
98
+ */
99
+ cancel(animationId) {
100
+ if (this.activeAnimations.has(animationId)) {
101
+ this.activeAnimations.delete(animationId);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Cancels all animations
107
+ */
108
+ cancelAll() {
109
+ this.activeAnimations.clear();
110
+ }
111
+
112
+ /**
113
+ * Gets initial value for a property
114
+ * @private
115
+ * @param {HTMLElement} element - Element
116
+ * @param {string} property - Property name
117
+ * @returns {number} Initial value
118
+ */
119
+ _getInitialValue(element, property) {
120
+ if (!element || !element.style) return 0;
121
+ switch (property) {
122
+ case 'opacity':
123
+ return parseFloat(getComputedStyle(element).opacity) || 1;
124
+ case 'left':
125
+ return parseFloat(element.style.left) || 0;
126
+ case 'top':
127
+ return parseFloat(element.style.top) || 0;
128
+ case 'scale':
129
+ return 1;
130
+ default:
131
+ return 0;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Interpolates between two values
137
+ * @private
138
+ * @param {number} start - Start value
139
+ * @param {number} end - End value
140
+ * @param {number} progress - Progress (0-1)
141
+ * @returns {number} Interpolated value
142
+ */
143
+ _interpolateValue(start, end, progress) {
144
+ return start + (end - start) * progress;
145
+ }
146
+
147
+ /**
148
+ * Applies animated value to element
149
+ * @private
150
+ * @param {HTMLElement} element - Element
151
+ * @param {string} property - Property name
152
+ * @param {number} value - Value to apply
153
+ */
154
+ _applyValue(element, property, value) {
155
+ if (!element || !element.style) return;
156
+ switch (property) {
157
+ case 'opacity':
158
+ element.style.opacity = value;
159
+ break;
160
+ case 'left':
161
+ element.style.left = value + 'px';
162
+ break;
163
+ case 'top':
164
+ element.style.top = value + 'px';
165
+ break;
166
+ case 'scale':
167
+ element.style.transform = `scale(${value})`;
168
+ break;
169
+ default:
170
+ element.style[property] = value;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Cleans up resources
176
+ */
177
+ destroy() {
178
+ this.cancelAll();
179
+ }
180
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Service for managing board setup and DOM operations
3
+ * @module services/BoardService
4
+ * @since 2.0.0
5
+ */
6
+
7
+ import { BOARD_SIZE } from '../constants/positions.js';
8
+ import { DOMError, ValidationError } from '../errors/ChessboardError.js';
9
+ import { ERROR_MESSAGES } from '../errors/messages.js';
10
+ import Square from '../components/Square.js';
11
+
12
+ /**
13
+ * Service responsible for board DOM manipulation and setup
14
+ * @class
15
+ */
16
+ export class BoardService {
17
+ /**
18
+ * Creates a new BoardService instance
19
+ * @param {ChessboardConfig} config - Board configuration
20
+ */
21
+ constructor(config) {
22
+ this.config = config;
23
+ this.element = null;
24
+ this.squares = {};
25
+ }
26
+
27
+ /**
28
+ * Builds the board DOM element and attaches it to the configured container
29
+ * @throws {DOMError} When the container element cannot be found
30
+ */
31
+ buildBoard() {
32
+ console.log('BoardService.buildBoard: Looking for element with ID:', this.config.id_div);
33
+
34
+ this.element = document.getElementById(this.config.id_div);
35
+ if (!this.element) {
36
+ throw new DOMError(ERROR_MESSAGES.invalid_id_div + this.config.id_div, this.config.id_div);
37
+ }
38
+
39
+ this.resize(this.config.size);
40
+ this.element.className = "board";
41
+ }
42
+
43
+ /**
44
+ * Creates all 64 squares and adds them to the board
45
+ * @param {Function} coordConverter - Function to convert row/col to real coordinates
46
+ */
47
+ buildSquares(coordConverter) {
48
+ for (let row = 0; row < BOARD_SIZE.ROWS; row++) {
49
+ for (let col = 0; col < BOARD_SIZE.COLS; col++) {
50
+ const [squareRow, squareCol] = coordConverter(row, col);
51
+ const square = new Square(squareRow, squareCol);
52
+
53
+ this.squares[square.getId()] = square;
54
+ this.element.appendChild(square.element);
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Removes all squares from the board and cleans up their resources
61
+ * Best practice: always destroy JS objects and DOM nodes, and clear references.
62
+ */
63
+ removeSquares() {
64
+ for (const square of Object.values(this.squares)) {
65
+ // Always call destroy to remove DOM and clear piece reference
66
+ square.destroy();
67
+ }
68
+ this.squares = {};
69
+ }
70
+
71
+ /**
72
+ * Removes all content from the board element
73
+ * Best practice: clear DOM and force element to be re-fetched on next build.
74
+ */
75
+ removeBoard() {
76
+ if (this.element) {
77
+ this.element.innerHTML = '';
78
+ this.element = null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Resizes the board to the specified size
84
+ * @param {number|string} value - Size in pixels or 'auto'
85
+ * @throws {ValidationError} When size value is invalid
86
+ */
87
+ resize(value) {
88
+ if (value === 'auto') {
89
+ const size = this._calculateAutoSize();
90
+ this.resize(size);
91
+ } else if (typeof value !== 'number') {
92
+ throw new ValidationError(ERROR_MESSAGES.invalid_value + value, 'size', value);
93
+ } else {
94
+ document.documentElement.style.setProperty('--dimBoard', value + 'px');
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Calculates the optimal size when 'auto' is specified
100
+ * @private
101
+ * @returns {number} Calculated size in pixels
102
+ */
103
+ _calculateAutoSize() {
104
+ if (!this.element) return 400; // Default fallback
105
+
106
+ const { offsetWidth, offsetHeight } = this.element;
107
+
108
+ if (offsetWidth === 0) {
109
+ return offsetHeight || 400;
110
+ } else if (offsetHeight === 0) {
111
+ return offsetWidth;
112
+ } else {
113
+ return Math.min(offsetWidth, offsetHeight);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Gets a square by its ID
119
+ * @param {string} squareId - Square identifier (e.g., 'e4')
120
+ * @returns {Square|null} The square or null if not found
121
+ */
122
+ getSquare(squareId) {
123
+ return this.squares[squareId] || null;
124
+ }
125
+
126
+ /**
127
+ * Gets all squares
128
+ * @returns {Object.<string, Square>} All squares indexed by ID
129
+ */
130
+ getAllSquares() {
131
+ return { ...this.squares };
132
+ }
133
+
134
+ /**
135
+ * Applies a method to all squares
136
+ * @param {string} methodName - Name of the method to call on each square
137
+ * @param {...*} args - Arguments to pass to the method
138
+ */
139
+ applyToAllSquares(methodName, ...args) {
140
+ for (const square of Object.values(this.squares)) {
141
+ if (typeof square[methodName] === 'function') {
142
+ square[methodName](...args);
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Cleans up all resources
149
+ */
150
+ destroy() {
151
+ this.removeSquares();
152
+ this.removeBoard();
153
+ this.element = null;
154
+ this.squares = {};
155
+ }
156
+ }