@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
|
+
* CoordinateService - Handles coordinate conversions and board orientation logic
|
|
3
3
|
* @module services/CoordinateService
|
|
4
4
|
* @since 2.0.0
|
|
5
5
|
*/
|
|
@@ -10,19 +10,20 @@ import { ERROR_MESSAGES } from '../errors/messages.js';
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Service responsible for coordinate conversions and board orientation
|
|
13
|
-
* @class
|
|
13
|
+
* @class CoordinateService
|
|
14
14
|
*/
|
|
15
15
|
export class CoordinateService {
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @param {
|
|
17
|
+
* Create a new CoordinateService instance
|
|
18
|
+
* @param {Object} config - Board configuration
|
|
19
19
|
*/
|
|
20
20
|
constructor(config) {
|
|
21
|
+
/** @type {Object} */
|
|
21
22
|
this.config = config;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
+
* Convert logical coordinates to real coordinates based on board orientation
|
|
26
27
|
* @param {number} row - Row index (0-7)
|
|
27
28
|
* @param {number} col - Column index (0-7)
|
|
28
29
|
* @returns {Array<number>} Real coordinates [row, col] (1-8)
|
|
@@ -30,18 +31,16 @@ export class CoordinateService {
|
|
|
30
31
|
realCoord(row, col) {
|
|
31
32
|
let realRow = row;
|
|
32
33
|
let realCol = col;
|
|
33
|
-
|
|
34
34
|
if (this.isWhiteOriented()) {
|
|
35
35
|
realRow = 7 - row;
|
|
36
36
|
} else {
|
|
37
37
|
realCol = 7 - col;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
39
|
return [realRow + 1, realCol + 1];
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
43
|
+
* Convert board coordinates to square ID
|
|
45
44
|
* @param {number} row - Row index (0-7)
|
|
46
45
|
* @param {number} col - Column index (0-7)
|
|
47
46
|
* @returns {string} Square ID (e.g., 'e4')
|
|
@@ -49,7 +48,6 @@ export class CoordinateService {
|
|
|
49
48
|
getSquareID(row, col) {
|
|
50
49
|
row = parseInt(row);
|
|
51
50
|
col = parseInt(col);
|
|
52
|
-
|
|
53
51
|
if (this.isWhiteOriented()) {
|
|
54
52
|
row = 8 - row;
|
|
55
53
|
col = col + 1;
|
|
@@ -57,7 +55,6 @@ export class CoordinateService {
|
|
|
57
55
|
row = row + 1;
|
|
58
56
|
col = 8 - col;
|
|
59
57
|
}
|
|
60
|
-
|
|
61
58
|
if (col < 1 || col > 8 || row < 1 || row > 8) {
|
|
62
59
|
throw new ValidationError(
|
|
63
60
|
`Invalid board coordinates: row=${row}, col=${col}`,
|
|
@@ -65,13 +62,12 @@ export class CoordinateService {
|
|
|
65
62
|
{ row, col }
|
|
66
63
|
);
|
|
67
64
|
}
|
|
68
|
-
|
|
69
65
|
const letter = BOARD_LETTERS[col - 1];
|
|
70
66
|
return letter + row;
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
/**
|
|
74
|
-
*
|
|
70
|
+
* Convert square ID to board coordinates
|
|
75
71
|
* @param {string} squareId - Square ID (e.g., 'e4')
|
|
76
72
|
* @returns {Array<number>} Board coordinates [row, col] (0-7)
|
|
77
73
|
*/
|
|
@@ -83,10 +79,8 @@ export class CoordinateService {
|
|
|
83
79
|
squareId
|
|
84
80
|
);
|
|
85
81
|
}
|
|
86
|
-
|
|
87
82
|
const letter = squareId[0];
|
|
88
83
|
const number = parseInt(squareId[1]);
|
|
89
|
-
|
|
90
84
|
const col = BOARD_LETTERS.indexOf(letter);
|
|
91
85
|
if (col === -1) {
|
|
92
86
|
throw new ValidationError(
|
|
@@ -95,7 +89,6 @@ export class CoordinateService {
|
|
|
95
89
|
squareId
|
|
96
90
|
);
|
|
97
91
|
}
|
|
98
|
-
|
|
99
92
|
if (number < 1 || number > 8) {
|
|
100
93
|
throw new ValidationError(
|
|
101
94
|
ERROR_MESSAGES.invalid_square + squareId,
|
|
@@ -103,9 +96,7 @@ export class CoordinateService {
|
|
|
103
96
|
squareId
|
|
104
97
|
);
|
|
105
98
|
}
|
|
106
|
-
|
|
107
99
|
let row, boardCol;
|
|
108
|
-
|
|
109
100
|
if (this.isWhiteOriented()) {
|
|
110
101
|
row = 8 - number;
|
|
111
102
|
boardCol = col;
|
|
@@ -113,12 +104,11 @@ export class CoordinateService {
|
|
|
113
104
|
row = number - 1;
|
|
114
105
|
boardCol = 7 - col;
|
|
115
106
|
}
|
|
116
|
-
|
|
117
107
|
return [row, boardCol];
|
|
118
108
|
}
|
|
119
109
|
|
|
120
110
|
/**
|
|
121
|
-
*
|
|
111
|
+
* Convert pixel coordinates to square ID
|
|
122
112
|
* @param {number} x - X coordinate in pixels
|
|
123
113
|
* @param {number} y - Y coordinate in pixels
|
|
124
114
|
* @param {HTMLElement} boardElement - Board DOM element
|
|
@@ -126,21 +116,15 @@ export class CoordinateService {
|
|
|
126
116
|
*/
|
|
127
117
|
pixelToSquareID(x, y, boardElement) {
|
|
128
118
|
if (!boardElement) return null;
|
|
129
|
-
|
|
130
119
|
const rect = boardElement.getBoundingClientRect();
|
|
131
120
|
const { width, height } = rect;
|
|
132
|
-
|
|
133
|
-
// Check if coordinates are within board bounds
|
|
134
121
|
if (x < 0 || x >= width || y < 0 || y >= height) {
|
|
135
122
|
return null;
|
|
136
123
|
}
|
|
137
|
-
|
|
138
124
|
const squareWidth = width / 8;
|
|
139
125
|
const squareHeight = height / 8;
|
|
140
|
-
|
|
141
126
|
const col = Math.floor(x / squareWidth);
|
|
142
127
|
const row = Math.floor(y / squareHeight);
|
|
143
|
-
|
|
144
128
|
try {
|
|
145
129
|
return this.getSquareID(row, col);
|
|
146
130
|
} catch (error) {
|
|
@@ -149,25 +133,21 @@ export class CoordinateService {
|
|
|
149
133
|
}
|
|
150
134
|
|
|
151
135
|
/**
|
|
152
|
-
*
|
|
136
|
+
* Convert square ID to pixel coordinates
|
|
153
137
|
* @param {string} squareId - Square ID (e.g., 'e4')
|
|
154
138
|
* @param {HTMLElement} boardElement - Board DOM element
|
|
155
139
|
* @returns {Object|null} Pixel coordinates {x, y} or null if invalid
|
|
156
140
|
*/
|
|
157
141
|
squareIDToPixel(squareId, boardElement) {
|
|
158
142
|
if (!boardElement) return null;
|
|
159
|
-
|
|
160
143
|
try {
|
|
161
144
|
const [row, col] = this.getCoordinatesFromSquareID(squareId);
|
|
162
145
|
const rect = boardElement.getBoundingClientRect();
|
|
163
146
|
const { width, height } = rect;
|
|
164
|
-
|
|
165
147
|
const squareWidth = width / 8;
|
|
166
148
|
const squareHeight = height / 8;
|
|
167
|
-
|
|
168
149
|
const x = col * squareWidth;
|
|
169
150
|
const y = row * squareHeight;
|
|
170
|
-
|
|
171
151
|
return { x, y };
|
|
172
152
|
} catch (error) {
|
|
173
153
|
return null;
|
|
@@ -175,7 +155,7 @@ export class CoordinateService {
|
|
|
175
155
|
}
|
|
176
156
|
|
|
177
157
|
/**
|
|
178
|
-
*
|
|
158
|
+
* Get the center pixel coordinates of a square
|
|
179
159
|
* @param {string} squareId - Square ID (e.g., 'e4')
|
|
180
160
|
* @param {HTMLElement} boardElement - Board DOM element
|
|
181
161
|
* @returns {Object|null} Center coordinates {x, y} or null if invalid
|
|
@@ -183,11 +163,9 @@ export class CoordinateService {
|
|
|
183
163
|
getSquareCenter(squareId, boardElement) {
|
|
184
164
|
const coords = this.squareIDToPixel(squareId, boardElement);
|
|
185
165
|
if (!coords) return null;
|
|
186
|
-
|
|
187
166
|
const rect = boardElement.getBoundingClientRect();
|
|
188
167
|
const squareWidth = rect.width / 8;
|
|
189
168
|
const squareHeight = rect.height / 8;
|
|
190
|
-
|
|
191
169
|
return {
|
|
192
170
|
x: coords.x + squareWidth / 2,
|
|
193
171
|
y: coords.y + squareHeight / 2
|
|
@@ -195,7 +173,7 @@ export class CoordinateService {
|
|
|
195
173
|
}
|
|
196
174
|
|
|
197
175
|
/**
|
|
198
|
-
*
|
|
176
|
+
* Calculate the distance between two squares
|
|
199
177
|
* @param {string} fromSquare - Source square ID
|
|
200
178
|
* @param {string} toSquare - Target square ID
|
|
201
179
|
* @returns {number} Distance between squares
|
|
@@ -204,10 +182,8 @@ export class CoordinateService {
|
|
|
204
182
|
try {
|
|
205
183
|
const [fromRow, fromCol] = this.getCoordinatesFromSquareID(fromSquare);
|
|
206
184
|
const [toRow, toCol] = this.getCoordinatesFromSquareID(toSquare);
|
|
207
|
-
|
|
208
185
|
const rowDiff = Math.abs(toRow - fromRow);
|
|
209
186
|
const colDiff = Math.abs(toCol - fromCol);
|
|
210
|
-
|
|
211
187
|
return Math.sqrt(rowDiff * rowDiff + colDiff * colDiff);
|
|
212
188
|
} catch (error) {
|
|
213
189
|
return 0;
|
|
@@ -215,7 +191,7 @@ export class CoordinateService {
|
|
|
215
191
|
}
|
|
216
192
|
|
|
217
193
|
/**
|
|
218
|
-
*
|
|
194
|
+
* Check if the board is oriented from white's perspective
|
|
219
195
|
* @returns {boolean} True if white-oriented
|
|
220
196
|
*/
|
|
221
197
|
isWhiteOriented() {
|
|
@@ -223,7 +199,7 @@ export class CoordinateService {
|
|
|
223
199
|
}
|
|
224
200
|
|
|
225
201
|
/**
|
|
226
|
-
*
|
|
202
|
+
* Check if the board is oriented from black's perspective
|
|
227
203
|
* @returns {boolean} True if black-oriented
|
|
228
204
|
*/
|
|
229
205
|
isBlackOriented() {
|
|
@@ -231,31 +207,31 @@ export class CoordinateService {
|
|
|
231
207
|
}
|
|
232
208
|
|
|
233
209
|
/**
|
|
234
|
-
*
|
|
210
|
+
* Flip the board orientation
|
|
235
211
|
*/
|
|
236
212
|
flipOrientation() {
|
|
237
213
|
this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
|
|
238
214
|
}
|
|
239
215
|
|
|
240
216
|
/**
|
|
241
|
-
*
|
|
242
|
-
* @param {string} orientation - 'w'
|
|
243
|
-
* @throws {ValidationError}
|
|
217
|
+
* Set the board orientation
|
|
218
|
+
* @param {string} orientation - 'w', 'b', 'white', or 'black'
|
|
219
|
+
* @throws {ValidationError} If orientation is invalid
|
|
244
220
|
*/
|
|
245
221
|
setOrientation(orientation) {
|
|
246
|
-
|
|
222
|
+
const normalized = orientation === 'white' ? 'w' : orientation === 'black' ? 'b' : orientation;
|
|
223
|
+
if (normalized !== 'w' && normalized !== 'b') {
|
|
247
224
|
throw new ValidationError(
|
|
248
225
|
ERROR_MESSAGES.invalid_orientation + orientation,
|
|
249
226
|
'orientation',
|
|
250
227
|
orientation
|
|
251
228
|
);
|
|
252
229
|
}
|
|
253
|
-
|
|
254
|
-
this.config.orientation = orientation;
|
|
230
|
+
this.config.orientation = normalized;
|
|
255
231
|
}
|
|
256
232
|
|
|
257
233
|
/**
|
|
258
|
-
*
|
|
234
|
+
* Get the current orientation
|
|
259
235
|
* @returns {string} Current orientation ('w' or 'b')
|
|
260
236
|
*/
|
|
261
237
|
getOrientation() {
|
|
@@ -263,50 +239,21 @@ export class CoordinateService {
|
|
|
263
239
|
}
|
|
264
240
|
|
|
265
241
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param {string} orientation - New orientation ('w', 'b', 'white', 'black')
|
|
268
|
-
*/
|
|
269
|
-
setOrientation(orientation) {
|
|
270
|
-
// Normalize orientation
|
|
271
|
-
const normalizedOrientation = orientation === 'white' ? 'w' :
|
|
272
|
-
orientation === 'black' ? 'b' : orientation;
|
|
273
|
-
|
|
274
|
-
if (normalizedOrientation !== 'w' && normalizedOrientation !== 'b') {
|
|
275
|
-
throw new ValidationError(
|
|
276
|
-
ERROR_MESSAGES.invalid_orientation + orientation,
|
|
277
|
-
'orientation',
|
|
278
|
-
orientation
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
this.config.orientation = normalizedOrientation;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Flips the board orientation
|
|
287
|
-
*/
|
|
288
|
-
flipOrientation() {
|
|
289
|
-
this.config.orientation = this.isWhiteOriented() ? 'b' : 'w';
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Gets all square IDs in order
|
|
242
|
+
* Get all square IDs in order
|
|
294
243
|
* @returns {Array<string>} Array of all square IDs
|
|
295
244
|
*/
|
|
296
245
|
getAllSquareIDs() {
|
|
297
246
|
const squares = [];
|
|
298
|
-
|
|
299
247
|
for (let row = 0; row < 8; row++) {
|
|
300
248
|
for (let col = 0; col < 8; col++) {
|
|
301
249
|
squares.push(this.getSquareID(row, col));
|
|
302
250
|
}
|
|
303
251
|
}
|
|
304
|
-
|
|
305
252
|
return squares;
|
|
306
253
|
}
|
|
307
254
|
|
|
308
255
|
/**
|
|
309
|
-
*
|
|
256
|
+
* Get all squares in a specific rank (row)
|
|
310
257
|
* @param {number} rank - Rank number (1-8)
|
|
311
258
|
* @returns {Array<string>} Array of square IDs in the rank
|
|
312
259
|
*/
|
|
@@ -318,19 +265,16 @@ export class CoordinateService {
|
|
|
318
265
|
rank
|
|
319
266
|
);
|
|
320
267
|
}
|
|
321
|
-
|
|
322
268
|
const squares = [];
|
|
323
|
-
|
|
324
269
|
for (let col = 0; col < 8; col++) {
|
|
325
270
|
const row = this.isWhiteOriented() ? 8 - rank : rank - 1;
|
|
326
271
|
squares.push(this.getSquareID(row, col));
|
|
327
272
|
}
|
|
328
|
-
|
|
329
273
|
return squares;
|
|
330
274
|
}
|
|
331
275
|
|
|
332
276
|
/**
|
|
333
|
-
*
|
|
277
|
+
* Get all squares in a specific file (column)
|
|
334
278
|
* @param {string} file - File letter (a-h)
|
|
335
279
|
* @returns {Array<string>} Array of square IDs in the file
|
|
336
280
|
*/
|
|
@@ -343,13 +287,10 @@ export class CoordinateService {
|
|
|
343
287
|
file
|
|
344
288
|
);
|
|
345
289
|
}
|
|
346
|
-
|
|
347
290
|
const squares = [];
|
|
348
|
-
|
|
349
291
|
for (let row = 0; row < 8; row++) {
|
|
350
292
|
squares.push(this.getSquareID(row, col));
|
|
351
293
|
}
|
|
352
|
-
|
|
353
294
|
return squares;
|
|
354
295
|
}
|
|
355
296
|
}
|
|
@@ -9,6 +9,7 @@ import { DragOptimizations } from '../utils/cross-browser.js';
|
|
|
9
9
|
import { ValidationError } from '../errors/ChessboardError.js';
|
|
10
10
|
import Move from '../components/Move.js';
|
|
11
11
|
import Piece from '../components/Piece.js';
|
|
12
|
+
import { ListenerManager } from '../utils/listenerManager.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Service responsible for event handling and user interactions
|
|
@@ -35,8 +36,8 @@ export class EventService {
|
|
|
35
36
|
this.promoting = false;
|
|
36
37
|
this.isAnimating = false;
|
|
37
38
|
|
|
38
|
-
//
|
|
39
|
-
this.
|
|
39
|
+
// Listener manager per gestione centralizzata
|
|
40
|
+
this.listenerManager = new ListenerManager();
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
@@ -46,8 +47,8 @@ export class EventService {
|
|
|
46
47
|
* @param {Function} onPieceLeave - Callback for piece leave
|
|
47
48
|
*/
|
|
48
49
|
addListeners(onSquareClick, onPieceHover, onPieceLeave) {
|
|
49
|
-
//
|
|
50
|
-
this.
|
|
50
|
+
// Rimuovi tutti i listener esistenti
|
|
51
|
+
this.listenerManager.removeAll();
|
|
51
52
|
|
|
52
53
|
const squares = this.boardService.getAllSquares();
|
|
53
54
|
|
|
@@ -65,8 +66,6 @@ export class EventService {
|
|
|
65
66
|
* @param {Function} onPieceLeave - Leave callback
|
|
66
67
|
*/
|
|
67
68
|
_addSquareListeners(square, onSquareClick, onPieceHover, onPieceLeave) {
|
|
68
|
-
const listeners = [];
|
|
69
|
-
|
|
70
69
|
// Throttled hover handlers for performance
|
|
71
70
|
const throttledHover = rafThrottle((e) => {
|
|
72
71
|
if (!this.clicked && this.config.hints) {
|
|
@@ -136,26 +135,13 @@ export class EventService {
|
|
|
136
135
|
}
|
|
137
136
|
};
|
|
138
137
|
|
|
139
|
-
//
|
|
140
|
-
square.element
|
|
141
|
-
square.element
|
|
142
|
-
square.element
|
|
143
|
-
|
|
144
|
-
square.element
|
|
145
|
-
square.element
|
|
146
|
-
square.element.addEventListener('touchend', handleTouchEnd);
|
|
147
|
-
|
|
148
|
-
// Store listeners for cleanup
|
|
149
|
-
listeners.push(
|
|
150
|
-
{ element: square.element, type: 'mouseover', handler: throttledHover },
|
|
151
|
-
{ element: square.element, type: 'mouseout', handler: throttledLeave },
|
|
152
|
-
{ element: square.element, type: 'click', handler: handleClick },
|
|
153
|
-
{ element: square.element, type: 'touchstart', handler: handleTouchStart },
|
|
154
|
-
{ element: square.element, type: 'touchmove', handler: handleTouchMove },
|
|
155
|
-
{ element: square.element, type: 'touchend', handler: handleTouchEnd }
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
this.eventListeners.set(square.id, listeners);
|
|
138
|
+
// Usa ListenerManager per aggiungere tutti i listener
|
|
139
|
+
this.listenerManager.add(square.element, 'mouseover', throttledHover);
|
|
140
|
+
this.listenerManager.add(square.element, 'mouseout', throttledLeave);
|
|
141
|
+
this.listenerManager.add(square.element, 'click', handleClick);
|
|
142
|
+
this.listenerManager.add(square.element, 'touchstart', handleTouchStart);
|
|
143
|
+
this.listenerManager.add(square.element, 'touchmove', handleTouchMove);
|
|
144
|
+
this.listenerManager.add(square.element, 'touchend', handleTouchEnd);
|
|
159
145
|
}
|
|
160
146
|
|
|
161
147
|
/**
|
|
@@ -172,8 +158,6 @@ export class EventService {
|
|
|
172
158
|
*/
|
|
173
159
|
createDragFunction(square, piece, onDragStart, onDragMove, onDrop, onSnapback, onMove, onRemove) {
|
|
174
160
|
return (event) => {
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
|
|
177
161
|
if (!this.config.draggable || !piece || this.isAnimating) {
|
|
178
162
|
return;
|
|
179
163
|
}
|
|
@@ -183,7 +167,6 @@ export class EventService {
|
|
|
183
167
|
let from = originalFrom;
|
|
184
168
|
let to = square;
|
|
185
169
|
let previousHighlight = null;
|
|
186
|
-
|
|
187
170
|
const img = piece.element;
|
|
188
171
|
|
|
189
172
|
if (!this.moveService.canMove(from)) {
|
|
@@ -194,6 +177,15 @@ export class EventService {
|
|
|
194
177
|
const isTouch = event.type && event.type.startsWith('touch');
|
|
195
178
|
const startX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
|
|
196
179
|
const startY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
|
|
180
|
+
let dragStarted = false;
|
|
181
|
+
|
|
182
|
+
// --- Calculate offset between cursor/finger and piece top-left at drag start ---
|
|
183
|
+
const pieceRect = img.getBoundingClientRect();
|
|
184
|
+
const boardElement = this.boardService.element;
|
|
185
|
+
const boardRect = boardElement.getBoundingClientRect();
|
|
186
|
+
// Offset from cursor/finger to top-left of piece
|
|
187
|
+
const offsetX = startX - pieceRect.left;
|
|
188
|
+
const offsetY = startY - pieceRect.top;
|
|
197
189
|
|
|
198
190
|
// --- Touch scroll lock helper ---
|
|
199
191
|
const addScrollLock = () => {
|
|
@@ -205,10 +197,6 @@ export class EventService {
|
|
|
205
197
|
|
|
206
198
|
// --- MOVE HANDLER (mouse + touch unified) ---
|
|
207
199
|
const moveAt = (event) => {
|
|
208
|
-
const boardElement = this.boardService.element;
|
|
209
|
-
const squareSize = boardElement.offsetWidth / 8;
|
|
210
|
-
|
|
211
|
-
// Get mouse/touch coordinates
|
|
212
200
|
let clientX, clientY;
|
|
213
201
|
if (event.touches && event.touches[0]) {
|
|
214
202
|
clientX = event.touches[0].clientX;
|
|
@@ -217,77 +205,55 @@ export class EventService {
|
|
|
217
205
|
clientX = event.clientX;
|
|
218
206
|
clientY = event.clientY;
|
|
219
207
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
const x = clientX - boardRect.left - (squareSize / 2);
|
|
224
|
-
const y = clientY - boardRect.top - (squareSize / 2);
|
|
225
|
-
|
|
208
|
+
// Position the piece so the cursor/finger stays at the same relative point
|
|
209
|
+
const x = clientX - boardRect.left - offsetX;
|
|
210
|
+
const y = clientY - boardRect.top - offsetY;
|
|
226
211
|
img.style.left = x + 'px';
|
|
227
212
|
img.style.top = y + 'px';
|
|
228
|
-
|
|
229
213
|
return true;
|
|
230
214
|
};
|
|
231
215
|
|
|
232
216
|
// --- DRAG MOVE (mouse + touch) ---
|
|
217
|
+
const moveThreshold = 5; // px
|
|
233
218
|
const onPointerMove = (event) => {
|
|
234
|
-
// For touch, only handle the first finger
|
|
235
219
|
if (event.touches && event.touches.length > 1) return;
|
|
236
220
|
if (event.touches && !event.touches[0]) return;
|
|
237
|
-
if (event.type && event.type.startsWith('touch')) event.preventDefault();
|
|
238
|
-
|
|
239
221
|
const currentX = event.clientX || (event.touches && event.touches[0]?.clientX) || 0;
|
|
240
222
|
const currentY = event.clientY || (event.touches && event.touches[0]?.clientY) || 0;
|
|
241
223
|
const deltaX = Math.abs(currentX - startX);
|
|
242
224
|
const deltaY = Math.abs(currentY - startY);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (!isDragging && (deltaX > 3 || deltaY > 3)) {
|
|
225
|
+
if (!dragStarted && (deltaX > moveThreshold || deltaY > moveThreshold)) {
|
|
226
|
+
dragStarted = true;
|
|
246
227
|
isDragging = true;
|
|
247
|
-
|
|
228
|
+
if (isTouch) addScrollLock();
|
|
248
229
|
if (this.chessboard) this.chessboard._isDragging = true;
|
|
249
|
-
|
|
250
|
-
// Mostra hint all'inizio del drag se attivi
|
|
251
230
|
if (this.config.hints && typeof this.chessboard._boundOnPieceHover === 'function') {
|
|
252
231
|
this.chessboard._boundOnPieceHover(from);
|
|
253
232
|
}
|
|
254
|
-
|
|
255
|
-
// Set up drag state
|
|
256
233
|
if (!this.config.clickable) {
|
|
257
234
|
this.clicked = null;
|
|
258
235
|
this.clicked = from;
|
|
259
236
|
} else if (!this.clicked) {
|
|
260
237
|
this.clicked = from;
|
|
261
238
|
}
|
|
262
|
-
|
|
263
|
-
// Visual feedback
|
|
264
239
|
if (this.config.clickable) {
|
|
265
240
|
from.select();
|
|
266
241
|
}
|
|
267
|
-
|
|
268
|
-
// Prepare piece for dragging
|
|
269
242
|
img.style.position = 'absolute';
|
|
270
243
|
img.style.zIndex = '100';
|
|
271
244
|
img.classList.add('dragging');
|
|
272
|
-
|
|
273
245
|
DragOptimizations.enableForDrag(img);
|
|
274
|
-
|
|
275
|
-
// Lock scroll for touch
|
|
276
|
-
if (isTouch) addScrollLock();
|
|
277
|
-
|
|
278
|
-
// Call drag start callback
|
|
279
246
|
if (!onDragStart(square, piece)) {
|
|
280
247
|
return;
|
|
281
248
|
}
|
|
282
249
|
}
|
|
283
|
-
|
|
284
|
-
if (
|
|
285
|
-
|
|
250
|
+
// Only block scroll and call preventDefault after drag has started
|
|
251
|
+
if (dragStarted && isTouch && event.cancelable) {
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
}
|
|
254
|
+
if (!dragStarted) return;
|
|
286
255
|
if (!moveAt(event)) return;
|
|
287
|
-
|
|
288
256
|
// Update target square
|
|
289
|
-
const boardElement = this.boardService.element;
|
|
290
|
-
const boardRect = boardElement.getBoundingClientRect();
|
|
291
257
|
let clientX, clientY;
|
|
292
258
|
if (event.touches && event.touches[0]) {
|
|
293
259
|
clientX = event.touches[0].clientX;
|
|
@@ -298,22 +264,16 @@ export class EventService {
|
|
|
298
264
|
}
|
|
299
265
|
const x = clientX - boardRect.left;
|
|
300
266
|
const y = clientY - boardRect.top;
|
|
301
|
-
|
|
302
267
|
let newTo = null;
|
|
303
268
|
if (x >= 0 && x <= boardRect.width && y >= 0 && y <= boardRect.height) {
|
|
304
269
|
const squareId = this.coordinateService.pixelToSquareID(x, y, boardElement);
|
|
305
270
|
newTo = squareId ? this.boardService.getSquare(squareId) : null;
|
|
306
271
|
}
|
|
307
|
-
|
|
308
272
|
to = newTo;
|
|
309
273
|
onDragMove(from, to, piece);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
to?.highlight();
|
|
314
|
-
previousHighlight?.dehighlight();
|
|
315
|
-
previousHighlight = to;
|
|
316
|
-
}
|
|
274
|
+
if (to) to.highlight();
|
|
275
|
+
if (previousHighlight && previousHighlight !== to) previousHighlight.dehighlight();
|
|
276
|
+
previousHighlight = to;
|
|
317
277
|
};
|
|
318
278
|
|
|
319
279
|
// --- DRAG END (mouse + touch) ---
|
|
@@ -324,26 +284,19 @@ export class EventService {
|
|
|
324
284
|
document.removeEventListener('touchmove', onPointerMove);
|
|
325
285
|
window.removeEventListener('touchend', onPointerUp);
|
|
326
286
|
if (isTouch) removeScrollLock();
|
|
327
|
-
// Fine drag: sblocca update board
|
|
328
287
|
if (this.chessboard) this.chessboard._isDragging = false;
|
|
329
|
-
|
|
330
|
-
// Rimuovi hint alla fine del drag se attivi
|
|
331
288
|
if (this.config.hints && typeof this.chessboard._boundOnPieceLeave === 'function') {
|
|
332
289
|
this.chessboard._boundOnPieceLeave(from);
|
|
333
290
|
}
|
|
334
|
-
|
|
335
|
-
if (!isDragging) {
|
|
291
|
+
if (!dragStarted) {
|
|
336
292
|
return;
|
|
337
293
|
}
|
|
338
|
-
|
|
339
294
|
img.style.zIndex = '20';
|
|
340
295
|
img.classList.remove('dragging');
|
|
341
296
|
img.style.willChange = 'auto';
|
|
342
|
-
|
|
343
297
|
// Handle drop
|
|
344
298
|
const dropResult = onDrop(originalFrom, to, piece);
|
|
345
299
|
const isTrashDrop = !to && (this.config.dropOffBoard === 'trash' || dropResult === 'trash');
|
|
346
|
-
|
|
347
300
|
if (isTrashDrop) {
|
|
348
301
|
this._handleTrashDrop(originalFrom, onRemove);
|
|
349
302
|
} else if (!to) {
|
|
@@ -357,16 +310,12 @@ export class EventService {
|
|
|
357
310
|
}
|
|
358
311
|
};
|
|
359
312
|
|
|
360
|
-
//
|
|
313
|
+
// Attach listeners (mouse + touch)
|
|
361
314
|
window.addEventListener('mouseup', onPointerUp, { once: true });
|
|
362
315
|
document.addEventListener('mousemove', onPointerMove);
|
|
363
316
|
img.addEventListener('mouseup', onPointerUp, { once: true });
|
|
364
|
-
// Touch events
|
|
365
317
|
window.addEventListener('touchend', onPointerUp, { once: true });
|
|
366
318
|
document.addEventListener('touchmove', onPointerMove, { passive: false });
|
|
367
|
-
|
|
368
|
-
// Per robustezza: se il drag parte da touch, blocca subito lo scroll
|
|
369
|
-
if (isTouch) addScrollLock();
|
|
370
319
|
};
|
|
371
320
|
}
|
|
372
321
|
|
|
@@ -859,33 +808,21 @@ export class EventService {
|
|
|
859
808
|
* Removes all existing event listeners
|
|
860
809
|
*/
|
|
861
810
|
removeListeners() {
|
|
862
|
-
this.
|
|
863
|
-
listeners.forEach(({ element, type, handler }) => {
|
|
864
|
-
element.removeEventListener(type, handler);
|
|
865
|
-
});
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
this.eventListeners.clear();
|
|
811
|
+
this.listenerManager.removeAll();
|
|
869
812
|
}
|
|
870
813
|
|
|
871
814
|
/**
|
|
872
815
|
* Removes all event listeners
|
|
873
816
|
*/
|
|
874
817
|
removeAllListeners() {
|
|
875
|
-
this.
|
|
876
|
-
listeners.forEach(({ element, type, handler }) => {
|
|
877
|
-
element.removeEventListener(type, handler);
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
this.eventListeners.clear();
|
|
818
|
+
this.listenerManager.removeAll();
|
|
882
819
|
}
|
|
883
820
|
|
|
884
821
|
/**
|
|
885
822
|
* Cleans up resources
|
|
886
823
|
*/
|
|
887
824
|
destroy() {
|
|
888
|
-
this.
|
|
825
|
+
this.listenerManager.destroy();
|
|
889
826
|
this.clicked = null;
|
|
890
827
|
this.promoting = false;
|
|
891
828
|
this.isAnimating = false;
|