@alepot55/chessboardjs 2.3.6 → 2.3.8

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Service for input validation and data sanitization
2
+ * ValidationService - Handles input validation and data sanitization
3
3
  * @module services/ValidationService
4
4
  * @since 2.0.0
5
5
  */
@@ -51,45 +51,39 @@ const SIZE_CONSTRAINTS = Object.freeze({
51
51
  /**
52
52
  * Service responsible for validating inputs and data
53
53
  * Implements caching for performance optimization
54
- * @class
54
+ * @class ValidationService
55
55
  */
56
56
  export class ValidationService {
57
57
  /**
58
- * Creates a new ValidationService instance
58
+ * Create a new ValidationService instance
59
59
  */
60
60
  constructor() {
61
- // Cache for validation results to improve performance
62
61
  this._validationCache = new Map();
63
62
  this._cacheMaxSize = 1000;
64
-
65
- // Compile patterns for reuse
66
63
  this._patterns = VALIDATION_PATTERNS;
67
64
  this._validValues = VALID_VALUES;
68
65
  this._constraints = SIZE_CONSTRAINTS;
69
66
  }
70
67
 
71
68
  /**
72
- * Validates a square identifier with caching
69
+ * Validate a square identifier with caching
73
70
  * @param {string} square - Square to validate (e.g., 'e4')
74
71
  * @returns {boolean} True if valid
75
72
  */
76
73
  isValidSquare(square) {
77
74
  const cacheKey = `square:${square}`;
78
-
79
75
  if (this._validationCache.has(cacheKey)) {
80
76
  return this._validationCache.get(cacheKey);
81
77
  }
82
-
83
- const isValid = typeof square === 'string' &&
84
- square.length === 2 &&
85
- this._patterns.square.test(square);
86
-
78
+ const isValid = typeof square === 'string' &&
79
+ square.length === 2 &&
80
+ this._patterns.square.test(square);
87
81
  this._cacheValidationResult(cacheKey, isValid);
88
82
  return isValid;
89
83
  }
90
84
 
91
85
  /**
92
- * Validates a piece identifier with enhanced format support
86
+ * Validate a piece identifier with enhanced format support
93
87
  * @param {string} piece - Piece to validate (e.g., 'wK', 'bp')
94
88
  * @returns {boolean} True if valid
95
89
  */
@@ -97,28 +91,22 @@ export class ValidationService {
97
91
  if (typeof piece !== 'string' || piece.length !== 2) {
98
92
  return false;
99
93
  }
100
-
101
94
  const cacheKey = `piece:${piece}`;
102
-
103
95
  if (this._validationCache.has(cacheKey)) {
104
96
  return this._validationCache.get(cacheKey);
105
97
  }
106
-
107
98
  const [first, second] = piece.split('');
108
-
109
- // Check both formats: [type][color] and [color][type]
110
- const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
111
- PIECE_COLORS.includes(second);
112
- const format2 = PIECE_COLORS.includes(first) &&
113
- PIECE_TYPES.includes(second.toLowerCase());
114
-
99
+ const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
100
+ PIECE_COLORS.includes(second);
101
+ const format2 = PIECE_COLORS.includes(first) &&
102
+ PIECE_TYPES.includes(second.toLowerCase());
115
103
  const isValid = format1 || format2;
116
104
  this._cacheValidationResult(cacheKey, isValid);
117
105
  return isValid;
118
106
  }
119
107
 
120
108
  /**
121
- * Validates a FEN string with comprehensive checks
109
+ * Validate a FEN string with comprehensive checks
122
110
  * @param {string} fen - FEN string to validate
123
111
  * @returns {boolean} True if valid
124
112
  */
@@ -126,86 +114,64 @@ export class ValidationService {
126
114
  if (typeof fen !== 'string') {
127
115
  return false;
128
116
  }
129
-
130
117
  const cacheKey = `fen:${fen}`;
131
-
132
118
  if (this._validationCache.has(cacheKey)) {
133
119
  return this._validationCache.get(cacheKey);
134
120
  }
135
-
136
- // Basic pattern check
137
121
  if (!this._patterns.fen.test(fen)) {
138
122
  this._cacheValidationResult(cacheKey, false);
139
123
  return false;
140
124
  }
141
-
142
- // Additional FEN validation
143
125
  const isValid = this._validateFenStructure(fen);
144
126
  this._cacheValidationResult(cacheKey, isValid);
145
127
  return isValid;
146
128
  }
147
129
 
148
130
  /**
149
- * Validates FEN structure in detail
131
+ * Validate FEN structure in detail
150
132
  * @private
151
133
  * @param {string} fen - FEN string to validate
152
134
  * @returns {boolean} True if valid
153
135
  */
154
136
  _validateFenStructure(fen) {
155
137
  const parts = fen.split(' ');
156
-
157
138
  if (parts.length !== 6) {
158
139
  return false;
159
140
  }
160
-
161
- // Validate piece placement
162
141
  const ranks = parts[0].split('/');
163
142
  if (ranks.length !== 8) {
164
143
  return false;
165
144
  }
166
-
167
- // Validate each rank
168
145
  for (const rank of ranks) {
169
146
  if (!this._validateRank(rank)) {
170
147
  return false;
171
148
  }
172
149
  }
173
-
174
- // Validate active color
175
150
  if (!['w', 'b'].includes(parts[1])) {
176
151
  return false;
177
152
  }
178
-
179
- // Validate castling rights
180
153
  if (!/^[KQkq-]*$/.test(parts[2])) {
181
154
  return false;
182
155
  }
183
-
184
- // Validate en passant target
185
156
  if (parts[3] !== '-' && !this.isValidSquare(parts[3])) {
186
157
  return false;
187
158
  }
188
-
189
- // Validate halfmove clock and fullmove number
190
159
  const halfmove = parseInt(parts[4], 10);
191
160
  const fullmove = parseInt(parts[5], 10);
192
-
193
- return !isNaN(halfmove) && !isNaN(fullmove) &&
194
- halfmove >= 0 && fullmove >= 1;
161
+ return !isNaN(halfmove) && !isNaN(fullmove) &&
162
+ halfmove >= 0 && fullmove >= 1;
195
163
  }
196
164
 
197
165
  /**
198
- * Validates a single rank in FEN notation
166
+ * Validate a single rank in FEN notation
199
167
  * @private
200
168
  * @param {string} rank - Rank to validate
201
169
  * @returns {boolean} True if valid
202
170
  */
203
171
  _validateRank(rank) {
204
172
  let squares = 0;
205
-
206
173
  for (let i = 0; i < rank.length; i++) {
207
174
  const char = rank[i];
208
-
209
175
  if (/[1-8]/.test(char)) {
210
176
  squares += parseInt(char, 10);
211
177
  } else if (/[prnbqkPRNBQK]/.test(char)) {
@@ -214,12 +180,11 @@ export class ValidationService {
214
180
  return false;
215
181
  }
216
182
  }
217
-
218
183
  return squares === 8;
219
184
  }
220
185
 
221
186
  /**
222
- * Validates a move string with comprehensive format support
187
+ * Validate a move string with comprehensive format support
223
188
  * @param {string} move - Move string to validate (e.g., 'e2e4', 'e7e8q')
224
189
  * @returns {boolean} True if valid
225
190
  */
@@ -227,20 +192,17 @@ export class ValidationService {
227
192
  if (typeof move !== 'string') {
228
193
  return false;
229
194
  }
230
-
231
195
  const cacheKey = `move:${move}`;
232
-
233
196
  if (this._validationCache.has(cacheKey)) {
234
197
  return this._validationCache.get(cacheKey);
235
198
  }
236
-
237
199
  const isValid = this._validateMoveFormat(move);
238
200
  this._cacheValidationResult(cacheKey, isValid);
239
201
  return isValid;
240
202
  }
241
203
 
242
204
  /**
243
- * Validates move format in detail
205
+ * Validate move format in detail
244
206
  * @private
245
207
  * @param {string} move - Move string to validate
246
208
  * @returns {boolean} True if valid
@@ -249,21 +211,15 @@ export class ValidationService {
249
211
  if (move.length < 4 || move.length > 5) {
250
212
  return false;
251
213
  }
252
-
253
- const from = move.slice(0, 2);
254
- const to = move.slice(2, 4);
255
- const promotion = move.slice(4, 5);
256
-
214
+ const from = move.substring(0, 2);
215
+ const to = move.substring(2, 4);
257
216
  if (!this.isValidSquare(from) || !this.isValidSquare(to)) {
258
217
  return false;
259
218
  }
260
-
261
- if (promotion && !this._validValues.promotionPieces.includes(promotion)) {
219
+ if (move.length === 5 && !/[qrnb]/i.test(move[4])) {
262
220
  return false;
263
221
  }
264
-
265
- // Additional move validation (e.g., source and target different)
266
- return from !== to;
222
+ return true;
267
223
  }
268
224
 
269
225
  /**
@@ -293,11 +249,9 @@ export class ValidationService {
293
249
  if (size === 'auto') {
294
250
  return true;
295
251
  }
296
-
297
252
  if (typeof size === 'number') {
298
253
  return size >= this._constraints.min && size <= this._constraints.max;
299
254
  }
300
-
301
255
  return false;
302
256
  }
303
257
 
@@ -307,9 +261,9 @@ export class ValidationService {
307
261
  * @returns {boolean} True if valid
308
262
  */
309
263
  isValidTime(time) {
310
- return typeof time === 'number' &&
311
- time >= 0 &&
312
- time <= this._constraints.maxTime;
264
+ return typeof time === 'number' &&
265
+ time >= 0 &&
266
+ time <= this._constraints.maxTime;
313
267
  }
314
268
 
315
269
  /**
@@ -327,10 +281,10 @@ export class ValidationService {
327
281
  * @returns {boolean} True if valid
328
282
  */
329
283
  isValidElementId(id) {
330
- return typeof id === 'string' &&
331
- id.length > 0 &&
332
- id.length <= 100 && // Reasonable length limit
333
- /^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
284
+ return typeof id === 'string' &&
285
+ id.length > 0 &&
286
+ id.length <= 100 && // Reasonable length limit
287
+ /^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
334
288
  }
335
289
 
336
290
  /**
@@ -370,24 +324,20 @@ export class ValidationService {
370
324
  if (typeof color !== 'string') {
371
325
  return false;
372
326
  }
373
-
374
327
  // Check for hex colors
375
328
  if (this._patterns.color.test(color)) {
376
329
  return true;
377
330
  }
378
-
379
331
  // Check for named colors (basic set)
380
332
  const namedColors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta'];
381
333
  if (namedColors.includes(color.toLowerCase())) {
382
334
  return true;
383
335
  }
384
-
385
336
  // Check for rgb/rgba format
386
337
  if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color) ||
387
338
  /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-1](\.\d+)?\s*\)$/.test(color)) {
388
339
  return true;
389
340
  }
390
-
391
341
  return false;
392
342
  }
393
343
 
@@ -400,8 +350,8 @@ export class ValidationService {
400
350
  validateSquare(square) {
401
351
  if (!this.isValidSquare(square)) {
402
352
  throw new ValidationError(
403
- ERROR_MESSAGES.invalid_square + square,
404
- 'square',
353
+ ERROR_MESSAGES.invalid_square + square,
354
+ 'square',
405
355
  square
406
356
  );
407
357
  }
@@ -417,8 +367,8 @@ export class ValidationService {
417
367
  validatePiece(piece) {
418
368
  if (!this.isValidPiece(piece)) {
419
369
  throw new ValidationError(
420
- ERROR_MESSAGES.invalid_piece + piece,
421
- 'piece',
370
+ ERROR_MESSAGES.invalid_piece + piece,
371
+ 'piece',
422
372
  piece
423
373
  );
424
374
  }
@@ -434,8 +384,8 @@ export class ValidationService {
434
384
  validateFen(fen) {
435
385
  if (!this.isValidFen(fen)) {
436
386
  throw new ValidationError(
437
- ERROR_MESSAGES.invalid_fen + fen,
438
- 'fen',
387
+ ERROR_MESSAGES.invalid_fen + fen,
388
+ 'fen',
439
389
  fen
440
390
  );
441
391
  }
@@ -451,8 +401,8 @@ export class ValidationService {
451
401
  validateMove(move) {
452
402
  if (!this.isValidMove(move)) {
453
403
  throw new ValidationError(
454
- ERROR_MESSAGES.invalid_move + move,
455
- 'move',
404
+ ERROR_MESSAGES.invalid_move + move,
405
+ 'move',
456
406
  move
457
407
  );
458
408
  }
@@ -468,16 +418,15 @@ export class ValidationService {
468
418
  validateOrientation(orientation) {
469
419
  if (!this.isValidOrientation(orientation)) {
470
420
  throw new ValidationError(
471
- ERROR_MESSAGES.invalid_orientation + orientation,
472
- 'orientation',
421
+ ERROR_MESSAGES.invalid_orientation + orientation,
422
+ 'orientation',
473
423
  orientation
474
424
  );
475
425
  }
476
-
477
426
  // Normalize to 'w' or 'b'
478
- return orientation === 'white' ? 'w' :
479
- orientation === 'black' ? 'b' :
480
- orientation;
427
+ return orientation === 'white' ? 'w' :
428
+ orientation === 'black' ? 'b' :
429
+ orientation;
481
430
  }
482
431
 
483
432
  /**
@@ -489,16 +438,15 @@ export class ValidationService {
489
438
  validateColor(color) {
490
439
  if (!this.isValidColor(color)) {
491
440
  throw new ValidationError(
492
- ERROR_MESSAGES.invalid_color + color,
493
- 'color',
441
+ ERROR_MESSAGES.invalid_color + color,
442
+ 'color',
494
443
  color
495
444
  );
496
445
  }
497
-
498
446
  // Normalize to 'w' or 'b'
499
- return color === 'white' ? 'w' :
500
- color === 'black' ? 'b' :
501
- color;
447
+ return color === 'white' ? 'w' :
448
+ color === 'black' ? 'b' :
449
+ color;
502
450
  }
503
451
 
504
452
  /**
@@ -510,8 +458,8 @@ export class ValidationService {
510
458
  validateSize(size) {
511
459
  if (!this.isValidSize(size)) {
512
460
  throw new ValidationError(
513
- ERROR_MESSAGES.invalid_size + size,
514
- 'size',
461
+ ERROR_MESSAGES.invalid_size + size,
462
+ 'size',
515
463
  size
516
464
  );
517
465
  }
@@ -527,36 +475,29 @@ export class ValidationService {
527
475
  validateConfig(config) {
528
476
  if (!config || typeof config !== 'object') {
529
477
  throw new ValidationError(
530
- 'Configuration must be an object',
531
- 'config',
478
+ 'Configuration must be an object',
479
+ 'config',
532
480
  config
533
481
  );
534
482
  }
535
-
536
483
  const errors = [];
537
-
538
484
  // Validate required fields
539
485
  if (!config.id && !config.id_div) {
540
486
  errors.push('Configuration must include id or id_div');
541
487
  }
542
-
543
488
  // Validate optional fields
544
489
  if (config.orientation && !this.isValidOrientation(config.orientation)) {
545
490
  errors.push(ERROR_MESSAGES.invalid_orientation + config.orientation);
546
491
  }
547
-
548
492
  if (config.size && !this.isValidSize(config.size)) {
549
493
  errors.push(ERROR_MESSAGES.invalid_size + config.size);
550
494
  }
551
-
552
495
  if (config.movableColors && !this.isValidMovableColors(config.movableColors)) {
553
496
  errors.push(ERROR_MESSAGES.invalid_color + config.movableColors);
554
497
  }
555
-
556
498
  if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
557
499
  errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
558
500
  }
559
-
560
501
  // Validate callbacks
561
502
  const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
562
503
  for (const callback of callbacks) {
@@ -564,7 +505,6 @@ export class ValidationService {
564
505
  errors.push(`Invalid ${callback} callback`);
565
506
  }
566
507
  }
567
-
568
508
  // Validate colors
569
509
  const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
570
510
  for (const field of colorFields) {
@@ -572,15 +512,13 @@ export class ValidationService {
572
512
  errors.push(`Invalid ${field} color: ${config[field]}`);
573
513
  }
574
514
  }
575
-
576
515
  if (errors.length > 0) {
577
516
  throw new ValidationError(
578
- `Configuration validation failed: ${errors.join(', ')}`,
579
- 'config',
517
+ `Configuration validation failed: ${errors.join(', ')}`,
518
+ 'config',
580
519
  config
581
520
  );
582
521
  }
583
-
584
522
  return config;
585
523
  }
586
524
 
@@ -596,7 +534,6 @@ export class ValidationService {
596
534
  const firstKey = this._validationCache.keys().next().value;
597
535
  this._validationCache.delete(firstKey);
598
536
  }
599
-
600
537
  this._validationCache.set(key, result);
601
538
  }
602
539
 
@@ -628,7 +565,6 @@ export class ValidationService {
628
565
  return validations.map(validation => {
629
566
  try {
630
567
  const { type, value } = validation;
631
-
632
568
  switch (type) {
633
569
  case 'square':
634
570
  return { valid: this.isValidSquare(value), value };
@@ -12,3 +12,4 @@ export { MoveService } from './MoveService.js';
12
12
  export { PieceService } from './PieceService.js';
13
13
  export { PositionService } from './PositionService.js';
14
14
  export { ValidationService } from './ValidationService.js';
15
+ export { ListenerManager } from '../utils/listenerManager.js';
@@ -105,4 +105,12 @@
105
105
 
106
106
  .highlighted {
107
107
  box-shadow: inset 0 0 10px var(--highlightSquare);
108
+ }
109
+
110
+ body.chessboardjs-dragging {
111
+ touch-action: none !important;
112
+ overscroll-behavior: contain !important;
113
+ position: fixed !important;
114
+ width: 100vw !important;
115
+ overflow: hidden !important;
108
116
  }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ListenerManager - Utility per la gestione centralizzata dei listener DOM
3
+ * Permette di aggiungere, rimuovere e pulire tutti i listener in modo sicuro e senza duplicazioni.
4
+ * @module utils/listenerManager
5
+ * @since 2.0.0
6
+ */
7
+
8
+ export class ListenerManager {
9
+ constructor() {
10
+ /**
11
+ * Lista di tutti i listener registrati
12
+ * @type {Array<{element: EventTarget, type: string, handler: Function, options?: any}>}
13
+ */
14
+ this.listeners = [];
15
+ }
16
+
17
+ /**
18
+ * Aggiunge un listener e lo traccia per la rimozione automatica
19
+ * @param {EventTarget} element - Elemento su cui aggiungere il listener
20
+ * @param {string} type - Tipo di evento
21
+ * @param {Function} handler - Funzione handler
22
+ * @param {Object|boolean} [options] - Opzioni per addEventListener
23
+ */
24
+ add(element, type, handler, options) {
25
+ if (!element || !type || !handler) return;
26
+ element.addEventListener(type, handler, options);
27
+ this.listeners.push({ element, type, handler, options });
28
+ }
29
+
30
+ /**
31
+ * Rimuove un listener specifico
32
+ * @param {EventTarget} element
33
+ * @param {string} type
34
+ * @param {Function} handler
35
+ */
36
+ remove(element, type, handler) {
37
+ if (!element || !type || !handler) return;
38
+ element.removeEventListener(type, handler);
39
+ this.listeners = this.listeners.filter(
40
+ l => !(l.element === element && l.type === type && l.handler === handler)
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Rimuove tutti i listener registrati
46
+ */
47
+ removeAll() {
48
+ for (const { element, type, handler, options } of this.listeners) {
49
+ element.removeEventListener(type, handler, options);
50
+ }
51
+ this.listeners = [];
52
+ }
53
+
54
+ /**
55
+ * Pulizia risorse
56
+ */
57
+ destroy() {
58
+ this.removeAll();
59
+ }
60
+ }
61
+
62
+ export default ListenerManager;