cm-chessboard 8.12.15 → 8.12.17
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/README.md +3 -0
- package/package.json +1 -1
- package/src/view/ChessboardView.js +14 -4
- package/src/view/VisualMoveInput.js +11 -13
- package/test/TestVisualMoveInput.js +37 -0
package/README.md
CHANGED
|
@@ -207,6 +207,9 @@ The event has the following **`event.type`**:
|
|
|
207
207
|
Return `true` or `false` to validate the start square. `false` cancels the move.
|
|
208
208
|
- **`INPUT_EVENT_TYPE.validateMoveInput`**: To validate the users move input. `event.squareFrom` and `event.squareTo`
|
|
209
209
|
contain the coordinates. Return `true` or `false` to validate the move. `false` cancels the move.
|
|
210
|
+
`event.probe` is `true` when the target square still holds one of the mover's own pieces (e.g. clicking another own
|
|
211
|
+
piece, or a chess960 castling target). A falsy result then means "re-select that piece" rather than an illegal move,
|
|
212
|
+
so you can suppress illegal-move feedback when `event.probe` is set.
|
|
210
213
|
- **`INPUT_EVENT_TYPE.moveInputCanceled`**: The user canceled the move with clicking again on the start square, clicking
|
|
211
214
|
outside the board or right click.
|
|
212
215
|
- **`INPUT_EVENT_TYPE.moveInputFinished`**: Fired after the move was made, also when canceled.
|
package/package.json
CHANGED
|
@@ -68,6 +68,10 @@ export class ChessboardView {
|
|
|
68
68
|
this.pointerDownListener = this.pointerDownHandler.bind(this)
|
|
69
69
|
this.container.addEventListener("mousedown", this.pointerDownListener)
|
|
70
70
|
this.container.addEventListener("touchstart", this.pointerDownListener, {passive: false})
|
|
71
|
+
// Suppress the native context menu on the whole board
|
|
72
|
+
// VisualMoveInput triggers a move cancel with a transient listener on right click
|
|
73
|
+
this.contextMenuListener = (e) => e.preventDefault()
|
|
74
|
+
this.container.addEventListener("contextmenu", this.contextMenuListener)
|
|
71
75
|
this.createSvgAndGroups()
|
|
72
76
|
this.handleResize()
|
|
73
77
|
}
|
|
@@ -91,8 +95,9 @@ export class ChessboardView {
|
|
|
91
95
|
if (this.resizeListener) {
|
|
92
96
|
window.removeEventListener("resize", this.resizeListener)
|
|
93
97
|
}
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
98
|
+
this.container.removeEventListener("mousedown", this.pointerDownListener)
|
|
99
|
+
this.container.removeEventListener("touchstart", this.pointerDownListener)
|
|
100
|
+
this.container.removeEventListener("contextmenu", this.contextMenuListener)
|
|
96
101
|
Svg.removeElement(this.svg)
|
|
97
102
|
this.container.remove()
|
|
98
103
|
}
|
|
@@ -405,13 +410,18 @@ export class ChessboardView {
|
|
|
405
410
|
this.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.moveInput, data)
|
|
406
411
|
}
|
|
407
412
|
|
|
408
|
-
validateMoveInputCallback(squareFrom, squareTo) {
|
|
413
|
+
validateMoveInputCallback(squareFrom, squareTo, probe = false) {
|
|
409
414
|
const data = {
|
|
410
415
|
chessboard: this.chessboard,
|
|
411
416
|
type: INPUT_EVENT_TYPE.validateMoveInput,
|
|
412
417
|
squareFrom: squareFrom,
|
|
413
418
|
squareTo: squareTo,
|
|
414
|
-
piece: this.chessboard.getPiece(squareFrom)
|
|
419
|
+
piece: this.chessboard.getPiece(squareFrom),
|
|
420
|
+
// true when this is only a speculative check whether the clicked
|
|
421
|
+
// same-color piece is a legal target (e.g. chess960 castling). A
|
|
422
|
+
// falsy result then means "re-select this piece", not an illegal
|
|
423
|
+
// move, so consumers can suppress illegal-move feedback.
|
|
424
|
+
probe: probe
|
|
415
425
|
}
|
|
416
426
|
if (this.chessboard.state.moveInputCallback) {
|
|
417
427
|
data.moveInputCallbackResult = this.chessboard.state.moveInputCallback(data)
|
|
@@ -59,8 +59,8 @@ export class VisualMoveInput {
|
|
|
59
59
|
this.view.movingOverSquareCallback(fromSquare, toSquare)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
validateMoveInputCallback(fromSquare, toSquare) {
|
|
63
|
-
const result = this.view.validateMoveInputCallback(fromSquare, toSquare)
|
|
62
|
+
validateMoveInputCallback(fromSquare, toSquare, probe = false) {
|
|
63
|
+
const result = this.view.validateMoveInputCallback(fromSquare, toSquare, probe)
|
|
64
64
|
this.chessboard.state.moveInputProcess.resolve(result)
|
|
65
65
|
return result
|
|
66
66
|
}
|
|
@@ -208,7 +208,7 @@ export class VisualMoveInput {
|
|
|
208
208
|
this.pointerCancelListener = null
|
|
209
209
|
}
|
|
210
210
|
if (this.contextMenuListener) {
|
|
211
|
-
removeEventListener("contextmenu", this.contextMenuListener)
|
|
211
|
+
this.chessboard.view.svg.removeEventListener("contextmenu", this.contextMenuListener)
|
|
212
212
|
this.contextMenuListener = null
|
|
213
213
|
}
|
|
214
214
|
this.setMoveInputState(MOVE_INPUT_STATE.waitForInputStart)
|
|
@@ -298,8 +298,10 @@ export class VisualMoveInput {
|
|
|
298
298
|
const startPieceName = this.chessboard.getPiece(this.fromSquare)
|
|
299
299
|
const startPieceColor = startPieceName ? startPieceName.substring(0, 1) : null
|
|
300
300
|
if (color && startPieceColor === pieceColor) {
|
|
301
|
-
// added to allow chess960 castling
|
|
302
|
-
|
|
301
|
+
// added to allow chess960 castling. This is only a probe:
|
|
302
|
+
// a falsy result means the user re-selected another own
|
|
303
|
+
// piece, not that an illegal move was attempted.
|
|
304
|
+
const result = this.validateMoveInputCallback(this.fromSquare, square, true)
|
|
303
305
|
if(!result) {
|
|
304
306
|
this.moveInputCanceledCallback(this.fromSquare, square, MOVE_CANCELED_REASON.clickedAnotherPiece)
|
|
305
307
|
if (this.moveInputStartedCallback(square)) {
|
|
@@ -384,14 +386,10 @@ export class VisualMoveInput {
|
|
|
384
386
|
if (square) {
|
|
385
387
|
if (this.moveInputState === MOVE_INPUT_STATE.dragTo || this.moveInputState === MOVE_INPUT_STATE.clickDragTo) {
|
|
386
388
|
if (this.fromSquare === square) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.setMoveInputState(MOVE_INPUT_STATE.reset)
|
|
392
|
-
} else {
|
|
393
|
-
this.setMoveInputState(MOVE_INPUT_STATE.clickTo, {square: square})
|
|
394
|
-
}
|
|
389
|
+
this.chessboard.state.position.setPiece(this.fromSquare, this.movedPiece)
|
|
390
|
+
this.view.setPieceVisibility(this.fromSquare)
|
|
391
|
+
this.moveInputCanceledCallback(square, null, MOVE_CANCELED_REASON.draggedBack)
|
|
392
|
+
this.setMoveInputState(MOVE_INPUT_STATE.reset)
|
|
395
393
|
} else {
|
|
396
394
|
this.setMoveInputState(MOVE_INPUT_STATE.moveDone, {square: square})
|
|
397
395
|
}
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import {describe, it, assert} from "../node_modules/teevi/src/teevi.js"
|
|
8
8
|
import {Chessboard} from "../src/Chessboard.js"
|
|
9
|
+
import {COLOR, INPUT_EVENT_TYPE} from "../src/view/ChessboardView.js"
|
|
9
10
|
import {FEN} from "../src/model/Position.js"
|
|
10
11
|
|
|
11
12
|
const STATE_WAIT_FOR_INPUT_START = "waitForInputStart"
|
|
12
13
|
const STATE_PIECE_CLICKED_THRESHOLD = "pieceClickedThreshold"
|
|
14
|
+
const STATE_CLICK_TO = "clickTo"
|
|
13
15
|
|
|
14
16
|
describe("TestVisualMoveInput", () => {
|
|
15
17
|
|
|
@@ -37,4 +39,39 @@ describe("TestVisualMoveInput", () => {
|
|
|
37
39
|
chessboard.destroy()
|
|
38
40
|
})
|
|
39
41
|
|
|
42
|
+
it("should mark the same-color reselect validation as a probe", async () => {
|
|
43
|
+
const chessboard = new Chessboard(document.getElementById("TestBoard"), {
|
|
44
|
+
assetsUrl: "../assets/",
|
|
45
|
+
position: FEN.start
|
|
46
|
+
})
|
|
47
|
+
let probeSeen
|
|
48
|
+
chessboard.enableMoveInput((event) => {
|
|
49
|
+
if (event.type === INPUT_EVENT_TYPE.validateMoveInput) {
|
|
50
|
+
probeSeen = event.probe
|
|
51
|
+
return false // reject -> this is a re-selection, not a real move
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
}, COLOR.white)
|
|
55
|
+
const visualMoveInput = chessboard.view.visualMoveInput
|
|
56
|
+
|
|
57
|
+
// simulate an existing click-to-move selection of the white e2 pawn
|
|
58
|
+
// (moveInputStartedCallback sets up the internal moveInputProcess task)
|
|
59
|
+
visualMoveInput.moveInputStartedCallback("e2")
|
|
60
|
+
visualMoveInput.moveInputState = STATE_CLICK_TO
|
|
61
|
+
visualMoveInput.fromSquare = "e2"
|
|
62
|
+
|
|
63
|
+
// click another own (white) piece, the d1 queen -> chess960 probe branch
|
|
64
|
+
visualMoveInput.onPointerDown({
|
|
65
|
+
type: "touchstart",
|
|
66
|
+
target: {getAttribute: (name) => (name === "data-square" ? "d1" : null)},
|
|
67
|
+
touches: [{clientX: 100, clientY: 100}],
|
|
68
|
+
preventDefault: () => {}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
assert.equal(probeSeen, true)
|
|
72
|
+
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve))
|
|
74
|
+
chessboard.destroy()
|
|
75
|
+
})
|
|
76
|
+
|
|
40
77
|
})
|