@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.
- package/dist/chessboard.cjs.js +437 -806
- package/dist/chessboard.css +8 -0
- package/dist/chessboard.esm.js +437 -806
- package/dist/chessboard.iife.js +437 -806
- package/dist/chessboard.umd.js +437 -806
- package/package.json +1 -1
- package/src/core/Chessboard.js +50 -12
- package/src/services/AnimationService.js +57 -82
- package/src/services/BoardService.js +40 -34
- package/src/services/CoordinateService.js +25 -84
- package/src/services/EventService.js +41 -104
- package/src/services/MoveService.js +63 -266
- package/src/services/PieceService.js +21 -64
- package/src/services/PositionService.js +26 -40
- package/src/services/ValidationService.js +54 -118
- package/src/services/index.js +1 -0
- package/src/styles/board.css +8 -0
- package/src/utils/listenerManager.js +62 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
194
|
-
halfmove >= 0 && fullmove >= 1;
|
|
161
|
+
return !isNaN(halfmove) && !isNaN(fullmove) &&
|
|
162
|
+
halfmove >= 0 && fullmove >= 1;
|
|
195
163
|
}
|
|
196
164
|
|
|
197
165
|
/**
|
|
198
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
312
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
480
|
-
|
|
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
|
-
|
|
501
|
-
|
|
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 };
|
package/src/services/index.js
CHANGED
|
@@ -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';
|
package/src/styles/board.css
CHANGED
|
@@ -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;
|