@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,673 @@
1
+ /**
2
+ * Service for input validation and data sanitization
3
+ * @module services/ValidationService
4
+ * @since 2.0.0
5
+ */
6
+
7
+ import { PIECE_TYPES, PIECE_COLORS, BOARD_LETTERS } from '../constants/positions.js';
8
+ import { ValidationError } from '../errors/ChessboardError.js';
9
+ import { ERROR_MESSAGES, ERROR_CODES } from '../errors/messages.js';
10
+
11
+ /**
12
+ * Validation patterns compiled for performance
13
+ * @constant
14
+ * @type {Object}
15
+ */
16
+ const VALIDATION_PATTERNS = Object.freeze({
17
+ square: /^[a-h][1-8]$/,
18
+ piece: /^[prnbqkPRNBQK][wb]$/,
19
+ fen: /^([rnbqkpRNBQKP1-8]+\/){7}[rnbqkpRNBQKP1-8]+\s[wb]\s[KQkq-]+\s[a-h1-8-]+\s\d+\s\d+$/,
20
+ move: /^[a-h][1-8][a-h][1-8][qrnb]?$/,
21
+ color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
22
+ });
23
+
24
+ /**
25
+ * Valid values for various configuration options
26
+ * @constant
27
+ * @type {Object}
28
+ */
29
+ const VALID_VALUES = Object.freeze({
30
+ orientations: ['w', 'b', 'white', 'black'],
31
+ colors: ['w', 'b', 'white', 'black'],
32
+ movableColors: ['w', 'b', 'white', 'black', 'both', 'none'],
33
+ dropOffBoard: ['snapback', 'trash'],
34
+ easingTypes: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
35
+ animationStyles: ['sequential', 'simultaneous'],
36
+ modes: ['normal', 'creative', 'analysis'],
37
+ promotionPieces: ['q', 'r', 'b', 'n', 'Q', 'R', 'B', 'N']
38
+ });
39
+
40
+ /**
41
+ * Size constraints for validation
42
+ * @constant
43
+ * @type {Object}
44
+ */
45
+ const SIZE_CONSTRAINTS = Object.freeze({
46
+ min: 50,
47
+ max: 2000,
48
+ maxTime: 10000,
49
+ maxDelay: 5000
50
+ });
51
+
52
+ /**
53
+ * Service responsible for validating inputs and data
54
+ * Implements caching for performance optimization
55
+ * @class
56
+ */
57
+ export class ValidationService {
58
+ /**
59
+ * Creates a new ValidationService instance
60
+ */
61
+ constructor() {
62
+ // Cache for validation results to improve performance
63
+ this._validationCache = new Map();
64
+ this._cacheMaxSize = 1000;
65
+
66
+ // Compile patterns for reuse
67
+ this._patterns = VALIDATION_PATTERNS;
68
+ this._validValues = VALID_VALUES;
69
+ this._constraints = SIZE_CONSTRAINTS;
70
+ }
71
+
72
+ /**
73
+ * Validates a square identifier with caching
74
+ * @param {string} square - Square to validate (e.g., 'e4')
75
+ * @returns {boolean} True if valid
76
+ */
77
+ isValidSquare(square) {
78
+ const cacheKey = `square:${square}`;
79
+
80
+ if (this._validationCache.has(cacheKey)) {
81
+ return this._validationCache.get(cacheKey);
82
+ }
83
+
84
+ const isValid = typeof square === 'string' &&
85
+ square.length === 2 &&
86
+ this._patterns.square.test(square);
87
+
88
+ this._cacheValidationResult(cacheKey, isValid);
89
+ return isValid;
90
+ }
91
+
92
+ /**
93
+ * Validates a piece identifier with enhanced format support
94
+ * @param {string} piece - Piece to validate (e.g., 'wK', 'bp')
95
+ * @returns {boolean} True if valid
96
+ */
97
+ isValidPiece(piece) {
98
+ if (typeof piece !== 'string' || piece.length !== 2) {
99
+ return false;
100
+ }
101
+
102
+ const cacheKey = `piece:${piece}`;
103
+
104
+ if (this._validationCache.has(cacheKey)) {
105
+ return this._validationCache.get(cacheKey);
106
+ }
107
+
108
+ const [first, second] = piece.split('');
109
+
110
+ // Check both formats: [type][color] and [color][type]
111
+ const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
112
+ PIECE_COLORS.includes(second);
113
+ const format2 = PIECE_COLORS.includes(first) &&
114
+ PIECE_TYPES.includes(second.toLowerCase());
115
+
116
+ const isValid = format1 || format2;
117
+ this._cacheValidationResult(cacheKey, isValid);
118
+ return isValid;
119
+ }
120
+
121
+ /**
122
+ * Validates a FEN string with comprehensive checks
123
+ * @param {string} fen - FEN string to validate
124
+ * @returns {boolean} True if valid
125
+ */
126
+ isValidFen(fen) {
127
+ if (typeof fen !== 'string') {
128
+ return false;
129
+ }
130
+
131
+ const cacheKey = `fen:${fen}`;
132
+
133
+ if (this._validationCache.has(cacheKey)) {
134
+ return this._validationCache.get(cacheKey);
135
+ }
136
+
137
+ // Basic pattern check
138
+ if (!this._patterns.fen.test(fen)) {
139
+ this._cacheValidationResult(cacheKey, false);
140
+ return false;
141
+ }
142
+
143
+ // Additional FEN validation
144
+ const isValid = this._validateFenStructure(fen);
145
+ this._cacheValidationResult(cacheKey, isValid);
146
+ return isValid;
147
+ }
148
+
149
+ /**
150
+ * Validates FEN structure in detail
151
+ * @private
152
+ * @param {string} fen - FEN string to validate
153
+ * @returns {boolean} True if valid
154
+ */
155
+ _validateFenStructure(fen) {
156
+ const parts = fen.split(' ');
157
+
158
+ if (parts.length !== 6) {
159
+ return false;
160
+ }
161
+
162
+ // Validate piece placement
163
+ const ranks = parts[0].split('/');
164
+ if (ranks.length !== 8) {
165
+ return false;
166
+ }
167
+
168
+ // Validate each rank
169
+ for (const rank of ranks) {
170
+ if (!this._validateRank(rank)) {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ // Validate active color
176
+ if (!['w', 'b'].includes(parts[1])) {
177
+ return false;
178
+ }
179
+
180
+ // Validate castling rights
181
+ if (!/^[KQkq-]*$/.test(parts[2])) {
182
+ return false;
183
+ }
184
+
185
+ // Validate en passant target
186
+ if (parts[3] !== '-' && !this.isValidSquare(parts[3])) {
187
+ return false;
188
+ }
189
+
190
+ // Validate halfmove clock and fullmove number
191
+ const halfmove = parseInt(parts[4], 10);
192
+ const fullmove = parseInt(parts[5], 10);
193
+
194
+ return !isNaN(halfmove) && !isNaN(fullmove) &&
195
+ halfmove >= 0 && fullmove >= 1;
196
+ }
197
+
198
+ /**
199
+ * Validates a single rank in FEN notation
200
+ * @private
201
+ * @param {string} rank - Rank to validate
202
+ * @returns {boolean} True if valid
203
+ */
204
+ _validateRank(rank) {
205
+ let squares = 0;
206
+
207
+ for (let i = 0; i < rank.length; i++) {
208
+ const char = rank[i];
209
+
210
+ if (/[1-8]/.test(char)) {
211
+ squares += parseInt(char, 10);
212
+ } else if (/[prnbqkPRNBQK]/.test(char)) {
213
+ squares += 1;
214
+ } else {
215
+ return false;
216
+ }
217
+ }
218
+
219
+ return squares === 8;
220
+ }
221
+
222
+ /**
223
+ * Validates a move string with comprehensive format support
224
+ * @param {string} move - Move string to validate (e.g., 'e2e4', 'e7e8q')
225
+ * @returns {boolean} True if valid
226
+ */
227
+ isValidMove(move) {
228
+ if (typeof move !== 'string') {
229
+ return false;
230
+ }
231
+
232
+ const cacheKey = `move:${move}`;
233
+
234
+ if (this._validationCache.has(cacheKey)) {
235
+ return this._validationCache.get(cacheKey);
236
+ }
237
+
238
+ const isValid = this._validateMoveFormat(move);
239
+ this._cacheValidationResult(cacheKey, isValid);
240
+ return isValid;
241
+ }
242
+
243
+ /**
244
+ * Validates move format in detail
245
+ * @private
246
+ * @param {string} move - Move string to validate
247
+ * @returns {boolean} True if valid
248
+ */
249
+ _validateMoveFormat(move) {
250
+ if (move.length < 4 || move.length > 5) {
251
+ return false;
252
+ }
253
+
254
+ const from = move.slice(0, 2);
255
+ const to = move.slice(2, 4);
256
+ const promotion = move.slice(4, 5);
257
+
258
+ if (!this.isValidSquare(from) || !this.isValidSquare(to)) {
259
+ return false;
260
+ }
261
+
262
+ if (promotion && !this._validValues.promotionPieces.includes(promotion)) {
263
+ return false;
264
+ }
265
+
266
+ // Additional move validation (e.g., source and target different)
267
+ return from !== to;
268
+ }
269
+
270
+ /**
271
+ * Validates board orientation
272
+ * @param {string} orientation - Orientation to validate
273
+ * @returns {boolean} True if valid
274
+ */
275
+ isValidOrientation(orientation) {
276
+ return this._validValues.orientations.includes(orientation);
277
+ }
278
+
279
+ /**
280
+ * Validates a color value
281
+ * @param {string} color - Color to validate
282
+ * @returns {boolean} True if valid
283
+ */
284
+ isValidColor(color) {
285
+ return this._validValues.colors.includes(color);
286
+ }
287
+
288
+ /**
289
+ * Validates a size value with constraints
290
+ * @param {number|string} size - Size to validate
291
+ * @returns {boolean} True if valid
292
+ */
293
+ isValidSize(size) {
294
+ if (size === 'auto') {
295
+ return true;
296
+ }
297
+
298
+ if (typeof size === 'number') {
299
+ return size >= this._constraints.min && size <= this._constraints.max;
300
+ }
301
+
302
+ return false;
303
+ }
304
+
305
+ /**
306
+ * Validates a time value with constraints
307
+ * @param {number} time - Time value to validate
308
+ * @returns {boolean} True if valid
309
+ */
310
+ isValidTime(time) {
311
+ return typeof time === 'number' &&
312
+ time >= 0 &&
313
+ time <= this._constraints.maxTime;
314
+ }
315
+
316
+ /**
317
+ * Validates a callback function
318
+ * @param {Function} callback - Callback to validate
319
+ * @returns {boolean} True if valid
320
+ */
321
+ isValidCallback(callback) {
322
+ return typeof callback === 'function';
323
+ }
324
+
325
+ /**
326
+ * Validates a DOM element ID
327
+ * @param {string} id - Element ID to validate
328
+ * @returns {boolean} True if valid
329
+ */
330
+ isValidElementId(id) {
331
+ return typeof id === 'string' &&
332
+ id.length > 0 &&
333
+ id.length <= 100 && // Reasonable length limit
334
+ /^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
335
+ }
336
+
337
+ /**
338
+ * Validates movable colors configuration
339
+ * @param {string} colors - Colors value to validate
340
+ * @returns {boolean} True if valid
341
+ */
342
+ isValidMovableColors(colors) {
343
+ return this._validValues.movableColors.includes(colors);
344
+ }
345
+
346
+ /**
347
+ * Validates drop off board configuration
348
+ * @param {string} dropOff - Drop off board value to validate
349
+ * @returns {boolean} True if valid
350
+ */
351
+ isValidDropOffBoard(dropOff) {
352
+ return this._validValues.dropOffBoard.includes(dropOff);
353
+ }
354
+
355
+ /**
356
+ * Validates animation easing type
357
+ * @param {string} easing - Easing type to validate
358
+ * @returns {boolean} True if valid
359
+ */
360
+ isValidEasing(easing) {
361
+ return this._validValues.easingTypes.includes(easing);
362
+ }
363
+
364
+ /**
365
+ * Validates animation style
366
+ * @param {string} style - Animation style to validate
367
+ * @returns {boolean} True if valid
368
+ */
369
+ isValidAnimationStyle(style) {
370
+ return this._validValues.animationStyles.includes(style);
371
+ }
372
+
373
+ /**
374
+ * Validates CSS color format
375
+ * @param {string} color - Color string to validate
376
+ * @returns {boolean} True if valid
377
+ */
378
+ isValidCSSColor(color) {
379
+ if (typeof color !== 'string') {
380
+ return false;
381
+ }
382
+
383
+ // Check for hex colors
384
+ if (this._patterns.color.test(color)) {
385
+ return true;
386
+ }
387
+
388
+ // Check for named colors (basic set)
389
+ const namedColors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta'];
390
+ if (namedColors.includes(color.toLowerCase())) {
391
+ return true;
392
+ }
393
+
394
+ // Check for rgb/rgba format
395
+ if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color) ||
396
+ /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-1](\.\d+)?\s*\)$/.test(color)) {
397
+ return true;
398
+ }
399
+
400
+ return false;
401
+ }
402
+
403
+ /**
404
+ * Validates and sanitizes a square identifier
405
+ * @param {string} square - Square to validate
406
+ * @returns {string} Sanitized square
407
+ * @throws {ValidationError} If square is invalid
408
+ */
409
+ validateSquare(square) {
410
+ if (!this.isValidSquare(square)) {
411
+ throw new ValidationError(
412
+ ERROR_MESSAGES.invalid_square + square,
413
+ 'square',
414
+ square
415
+ );
416
+ }
417
+ return square.toLowerCase();
418
+ }
419
+
420
+ /**
421
+ * Validates and sanitizes a piece identifier
422
+ * @param {string} piece - Piece to validate
423
+ * @returns {string} Sanitized piece
424
+ * @throws {ValidationError} If piece is invalid
425
+ */
426
+ validatePiece(piece) {
427
+ if (!this.isValidPiece(piece)) {
428
+ throw new ValidationError(
429
+ ERROR_MESSAGES.invalid_piece + piece,
430
+ 'piece',
431
+ piece
432
+ );
433
+ }
434
+ return piece.toLowerCase();
435
+ }
436
+
437
+ /**
438
+ * Validates and sanitizes a FEN string
439
+ * @param {string} fen - FEN to validate
440
+ * @returns {string} Sanitized FEN
441
+ * @throws {ValidationError} If FEN is invalid
442
+ */
443
+ validateFen(fen) {
444
+ if (!this.isValidFen(fen)) {
445
+ throw new ValidationError(
446
+ ERROR_MESSAGES.invalid_fen + fen,
447
+ 'fen',
448
+ fen
449
+ );
450
+ }
451
+ return fen.trim();
452
+ }
453
+
454
+ /**
455
+ * Validates and sanitizes a move string
456
+ * @param {string} move - Move to validate
457
+ * @returns {string} Sanitized move
458
+ * @throws {ValidationError} If move is invalid
459
+ */
460
+ validateMove(move) {
461
+ if (!this.isValidMove(move)) {
462
+ throw new ValidationError(
463
+ ERROR_MESSAGES.invalid_move + move,
464
+ 'move',
465
+ move
466
+ );
467
+ }
468
+ return move.toLowerCase();
469
+ }
470
+
471
+ /**
472
+ * Validates and sanitizes orientation
473
+ * @param {string} orientation - Orientation to validate
474
+ * @returns {string} Sanitized orientation ('w' or 'b')
475
+ * @throws {ValidationError} If orientation is invalid
476
+ */
477
+ validateOrientation(orientation) {
478
+ if (!this.isValidOrientation(orientation)) {
479
+ throw new ValidationError(
480
+ ERROR_MESSAGES.invalid_orientation + orientation,
481
+ 'orientation',
482
+ orientation
483
+ );
484
+ }
485
+
486
+ // Normalize to 'w' or 'b'
487
+ return orientation === 'white' ? 'w' :
488
+ orientation === 'black' ? 'b' :
489
+ orientation;
490
+ }
491
+
492
+ /**
493
+ * Validates and sanitizes color
494
+ * @param {string} color - Color to validate
495
+ * @returns {string} Sanitized color ('w' or 'b')
496
+ * @throws {ValidationError} If color is invalid
497
+ */
498
+ validateColor(color) {
499
+ if (!this.isValidColor(color)) {
500
+ throw new ValidationError(
501
+ ERROR_MESSAGES.invalid_color + color,
502
+ 'color',
503
+ color
504
+ );
505
+ }
506
+
507
+ // Normalize to 'w' or 'b'
508
+ return color === 'white' ? 'w' :
509
+ color === 'black' ? 'b' :
510
+ color;
511
+ }
512
+
513
+ /**
514
+ * Validates and sanitizes size
515
+ * @param {number|string} size - Size to validate
516
+ * @returns {number|string} Sanitized size
517
+ * @throws {ValidationError} If size is invalid
518
+ */
519
+ validateSize(size) {
520
+ if (!this.isValidSize(size)) {
521
+ throw new ValidationError(
522
+ ERROR_MESSAGES.invalid_size + size,
523
+ 'size',
524
+ size
525
+ );
526
+ }
527
+ return size;
528
+ }
529
+
530
+ /**
531
+ * Validates configuration object with comprehensive checks
532
+ * @param {Object} config - Configuration to validate
533
+ * @returns {Object} Validated configuration
534
+ * @throws {ValidationError} If configuration is invalid
535
+ */
536
+ validateConfig(config) {
537
+ if (!config || typeof config !== 'object') {
538
+ throw new ValidationError(
539
+ 'Configuration must be an object',
540
+ 'config',
541
+ config
542
+ );
543
+ }
544
+
545
+ const errors = [];
546
+
547
+ // Validate required fields
548
+ if (!config.id && !config.id_div) {
549
+ errors.push('Configuration must include id or id_div');
550
+ }
551
+
552
+ // Validate optional fields
553
+ if (config.orientation && !this.isValidOrientation(config.orientation)) {
554
+ errors.push(ERROR_MESSAGES.invalid_orientation + config.orientation);
555
+ }
556
+
557
+ if (config.size && !this.isValidSize(config.size)) {
558
+ errors.push(ERROR_MESSAGES.invalid_size + config.size);
559
+ }
560
+
561
+ if (config.movableColors && !this.isValidMovableColors(config.movableColors)) {
562
+ errors.push(ERROR_MESSAGES.invalid_color + config.movableColors);
563
+ }
564
+
565
+ if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
566
+ errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
567
+ }
568
+
569
+ if (config.animationStyle && !this.isValidAnimationStyle(config.animationStyle)) {
570
+ errors.push(ERROR_MESSAGES.invalid_animationStyle + config.animationStyle);
571
+ }
572
+
573
+ // Validate callbacks
574
+ const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
575
+ for (const callback of callbacks) {
576
+ if (config[callback] && !this.isValidCallback(config[callback])) {
577
+ errors.push(`Invalid ${callback} callback`);
578
+ }
579
+ }
580
+
581
+ // Validate colors
582
+ const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
583
+ for (const field of colorFields) {
584
+ if (config[field] && !this.isValidCSSColor(config[field])) {
585
+ errors.push(`Invalid ${field} color: ${config[field]}`);
586
+ }
587
+ }
588
+
589
+ if (errors.length > 0) {
590
+ throw new ValidationError(
591
+ `Configuration validation failed: ${errors.join(', ')}`,
592
+ 'config',
593
+ config
594
+ );
595
+ }
596
+
597
+ return config;
598
+ }
599
+
600
+ /**
601
+ * Caches validation result for performance
602
+ * @private
603
+ * @param {string} key - Cache key
604
+ * @param {boolean} result - Validation result
605
+ */
606
+ _cacheValidationResult(key, result) {
607
+ if (this._validationCache.size >= this._cacheMaxSize) {
608
+ // Remove oldest entry
609
+ const firstKey = this._validationCache.keys().next().value;
610
+ this._validationCache.delete(firstKey);
611
+ }
612
+
613
+ this._validationCache.set(key, result);
614
+ }
615
+
616
+ /**
617
+ * Clears the validation cache
618
+ */
619
+ clearCache() {
620
+ this._validationCache.clear();
621
+ }
622
+
623
+ /**
624
+ * Gets validation cache statistics
625
+ * @returns {Object} Cache statistics
626
+ */
627
+ getCacheStats() {
628
+ return {
629
+ size: this._validationCache.size,
630
+ maxSize: this._cacheMaxSize,
631
+ hitRate: this._cacheHits / (this._cacheHits + this._cacheMisses) || 0
632
+ };
633
+ }
634
+
635
+ /**
636
+ * Batch validates multiple values
637
+ * @param {Array} validations - Array of validation objects
638
+ * @returns {Array} Array of validation results
639
+ */
640
+ batchValidate(validations) {
641
+ return validations.map(validation => {
642
+ try {
643
+ const { type, value } = validation;
644
+
645
+ switch (type) {
646
+ case 'square':
647
+ return { valid: this.isValidSquare(value), value };
648
+ case 'piece':
649
+ return { valid: this.isValidPiece(value), value };
650
+ case 'fen':
651
+ return { valid: this.isValidFen(value), value };
652
+ case 'move':
653
+ return { valid: this.isValidMove(value), value };
654
+ default:
655
+ return { valid: false, value, error: 'Unknown validation type' };
656
+ }
657
+ } catch (error) {
658
+ return { valid: false, value: validation.value, error: error.message };
659
+ }
660
+ });
661
+ }
662
+
663
+ /**
664
+ * Cleans up resources
665
+ */
666
+ destroy() {
667
+ this.clearCache();
668
+ this._validationCache = null;
669
+ this._patterns = null;
670
+ this._validValues = null;
671
+ this._constraints = null;
672
+ }
673
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Services module exports
3
+ * @module services
4
+ * @since 2.0.0
5
+ */
6
+
7
+ export { AnimationService } from './AnimationService.js';
8
+ export { BoardService } from './BoardService.js';
9
+ export { CoordinateService } from './CoordinateService.js';
10
+ export { EventService } from './EventService.js';
11
+ export { MoveService } from './MoveService.js';
12
+ export { PieceService } from './PieceService.js';
13
+ export { PositionService } from './PositionService.js';
14
+ export { ValidationService } from './ValidationService.js';