cm-chessboard 8.10.0 → 8.11.0

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(tree:*)"
5
+ ],
6
+ "deny": [],
7
+ "ask": []
8
+ }
9
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,207 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ cm-chessboard is a lightweight, dependency-free JavaScript chessboard library. It's ES6 module-based, responsive, SVG-rendered, and used in production on chessmail.eu/chessmail.de.
8
+
9
+ **Key Philosophy:** The core is intentionally minimal. Functionality is extended through an extension system rather than adding features to the core.
10
+
11
+ ## Development Commands
12
+
13
+ ### Running Tests
14
+
15
+ ```bash
16
+ # Tests use the Teevi framework and must be run in a browser
17
+ # Open test/index.html directly to run the test suite
18
+ ```
19
+
20
+ ### No Build Step
21
+ This project has no build process. It uses native ES6 modules that run directly in modern browsers.
22
+
23
+ ## Architecture
24
+
25
+ ### Core Components (MVC Pattern)
26
+
27
+ The codebase follows a clean MVC architecture:
28
+
29
+ 1. **Model Layer** (`src/model/`)
30
+ - `Position.js` - Board state representation using FEN notation. Contains 64-square array and FEN parsing/serialization
31
+ - `ChessboardState.js` - Manages application state (orientation, input flags, extension points)
32
+ - `Extension.js` - Base class for all extensions. Defines EXTENSION_POINT constants
33
+
34
+ 2. **View Layer** (`src/view/`)
35
+ - `ChessboardView.js` - SVG rendering, board drawing, piece rendering, coordinate display
36
+ - `VisualMoveInput.js` - Handles user interaction (drag & drop, click to move)
37
+ - `PositionAnimationsQueue.js` - Queues and executes piece animations
38
+
39
+ 3. **Controller** (`src/Chessboard.js`)
40
+ - Main API class that coordinates model and view
41
+ - Entry point for all public methods
42
+ - Instantiates extensions from props
43
+
44
+ 4. **Utilities** (`src/lib/`)
45
+ - `Svg.js` - SVG DOM manipulation helpers
46
+ - `Utils.js` - General utilities (URL handling, object merging)
47
+
48
+ ### Data Flow
49
+
50
+ 1. User calls API method on `Chessboard` instance
51
+ 2. `Chessboard` updates `Position` in `ChessboardState`
52
+ 3. Extension points are invoked via `state.invokeExtensionPoints()`
53
+ 4. Animation is queued in `PositionAnimationsQueue`
54
+ 5. `ChessboardView` renders the changes to SVG
55
+
56
+ ### Extension System
57
+
58
+ Extensions are the primary way to add functionality. They:
59
+ - Extend the `Extension` base class
60
+ - Register callbacks at extension points using `registerExtensionPoint(EXTENSION_POINT.*, callback)`
61
+ - Can add methods directly to the chessboard instance (e.g., `chessboard.addMarker = this.addMarker.bind(this)`)
62
+
63
+ **Available Extension Points** (defined in `Extension.js`):
64
+ - `positionChanged` - Piece positions changed
65
+ - `boardChanged` - Board orientation changed
66
+ - `boardResized` - Board was resized
67
+ - `moveInputToggled` - Move input enabled/disabled
68
+ - `moveInput` - Move events (started, validating, canceled, finished)
69
+ - `beforeRedrawBoard` / `afterRedrawBoard` - Board redraw lifecycle
70
+ - `animation` - Animation lifecycle hooks
71
+ - `destroy` - Cleanup before board destruction
72
+
73
+ **Included Extensions** (`src/extensions/`):
74
+ - `markers/` - Visual markers on squares (frames, circles, dots)
75
+ - `arrows/` - Drawing arrows between squares
76
+ - `right-click-annotator/` - Right-click UI for annotations (uses markers + arrows)
77
+ - `accessibility/` - Screen reader support, braille notation, keyboard input
78
+ - `promotion-dialog/` - UI dialog for pawn promotion
79
+ - `persistence/` - Save/restore board state to localStorage
80
+ - `html-layer/` - Overlay HTML content on the board
81
+ - `auto-border-none/` - Utility extension
82
+
83
+ ## Important Implementation Details
84
+
85
+ ### Coordinate System
86
+ - Squares are represented as strings: "a1", "h8", etc.
87
+ - Internally uses 0-63 index (Position.squareToIndex / indexToSquare)
88
+ - Orientation affects rendering but not internal representation
89
+
90
+ ### FEN Handling
91
+ - Position class only stores the piece placement (first part of full FEN)
92
+ - Full FEN format is supported in setPosition() but only piece positions are stored
93
+ - Use `FEN.start` and `FEN.empty` constants
94
+
95
+ ### Piece Naming
96
+ - Format: `{color}{type}` - e.g., "wp" (white pawn), "bk" (black king)
97
+ - Color: "w" or "b"
98
+ - Type: "p", "n", "b", "r", "q", "k"
99
+
100
+ ### SVG Rendering
101
+ - Pieces are rendered from SVG sprites (40x40px tiles)
102
+ - Sprite can be cached in a hidden div for performance
103
+ - Board uses multiple SVG groups as layers (board, coordinates, markers, pieces, etc.)
104
+ - Responsive sizing via ResizeObserver or window resize events
105
+
106
+ ### Move Input System
107
+ - Implemented in `VisualMoveInput.js`
108
+ - Supports both drag-and-drop and click-to-move
109
+ - Callback receives event objects with types from `INPUT_EVENT_TYPE`
110
+ - Returns true/false to validate moves
111
+ - Move input can be enabled per color (white/black/both)
112
+
113
+ ### Animation Queue
114
+ - All position changes are queued to prevent conflicts
115
+ - Animations return Promises that resolve when complete
116
+ - Queue ensures sequential execution even with rapid API calls
117
+
118
+ ## Writing Extensions
119
+
120
+ Template for a new extension:
121
+
122
+ ```javascript
123
+ import {Extension, EXTENSION_POINT} from "../../model/Extension.js"
124
+
125
+ export class MyExtension extends Extension {
126
+ constructor(chessboard, props = {}) {
127
+ super(chessboard)
128
+
129
+ // Set default props
130
+ this.props = {
131
+ myOption: true
132
+ }
133
+ Object.assign(this.props, props)
134
+
135
+ // Register extension points
136
+ this.registerExtensionPoint(EXTENSION_POINT.positionChanged, (data) => {
137
+ // React to position changes
138
+ })
139
+
140
+ // Add methods to chessboard instance
141
+ chessboard.myMethod = this.myMethod.bind(this)
142
+ }
143
+
144
+ myMethod() {
145
+ // Implementation
146
+ }
147
+ }
148
+ ```
149
+
150
+ Enable extension in chessboard props:
151
+ ```javascript
152
+ extensions: [{class: MyExtension, props: {myOption: false}}]
153
+ ```
154
+
155
+ ## Common Patterns
156
+
157
+ ### Adding a piece
158
+ ```javascript
159
+ board.setPiece("e4", "wn", true) // animated
160
+ ```
161
+
162
+ ### Moving pieces
163
+ ```javascript
164
+ board.movePiece("e2", "e4", true) // animated
165
+ ```
166
+
167
+ ### Setting entire position
168
+ ```javascript
169
+ board.setPosition(FEN.start, true) // animated
170
+ ```
171
+
172
+ ### Enabling move input
173
+ ```javascript
174
+ board.enableMoveInput((event) => {
175
+ if (event.type === INPUT_EVENT_TYPE.validateMoveInput) {
176
+ // Validate the move
177
+ return isLegalMove(event.squareFrom, event.squareTo)
178
+ }
179
+ }, COLOR.white) // Optional: restrict to one side
180
+ ```
181
+
182
+ ### Creating markers (requires Markers extension)
183
+ ```javascript
184
+ board.addMarker(MARKER_TYPE.dot, "e4")
185
+ board.removeMarkers(MARKER_TYPE.frame) // Remove by type
186
+ board.removeMarkers() // Remove all
187
+ ```
188
+
189
+ ## Assets Structure
190
+
191
+ - `assets/pieces/` - SVG sprite files for piece sets (standard.svg, staunty.svg)
192
+ - `assets/extensions/` - Assets needed by extensions (markers SVG, arrow SVG, etc.)
193
+ - `assets/chessboard.css` - Core styles
194
+ - Extension-specific CSS must be included separately when using extensions
195
+
196
+ ## Examples
197
+
198
+ The `examples/` directory contains working examples demonstrating:
199
+ - Simple board creation
200
+ - Move input and validation
201
+ - Different visual styles
202
+ - All included extensions
203
+ - Animation examples
204
+ - Responsive boards
205
+ - Multiple boards on one page
206
+
207
+ Reference examples when implementing new features or debugging issues.
package/README.md CHANGED
@@ -15,12 +15,13 @@ in [chess-console](https://shaack.com/projekte/chess-console/examples/load-pgn.h
15
15
  - Uses SVG for rendering
16
16
  - [Allows adding extensions to extend the
17
17
  functionality](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html)
18
+ - [Supports chess960 (Freestyle) castling input](https://shaack.com/projekte/cm-chessboard/examples/validate-moves-chess960.html)
18
19
 
19
20
  ## Extensions
20
21
 
21
- The core of cm-chessboard is small, fast and reduced to the essentials. You can easily extend its functionality with extensions.
22
+ The core of cm-chessboard is small, fast and reduced to the essentials. You can extend its functionality with extensions.
22
23
 
23
- - [RightClickAnnotator](https://shaack.com/projekte/cm-chessboard/examples/extensions/right-click-annotator.html) ⇨ Uses [Markers Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) and [Arrows Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html) and adds the handling mouse events to draw them on the board.
24
+ - [RightClickAnnotator](https://shaack.com/projekte/cm-chessboard/examples/extensions/right-click-annotator.html) ⇨ Uses [Markers Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) and [Arrows Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html). Adds the handling of mouse events to draw them on the board.
24
25
  - [Markers Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) ⇨ create markers on specific squares
25
26
  - [Arrows Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html) ⇨ renders arrows on the chessboard
26
27
  - [Accessibility Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/accessibility-extension.html) ⇨ makes the chessboard more accessible
@@ -0,0 +1,121 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>cm-chessboard</title>
6
+ <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0"/>
7
+ <link rel="stylesheet" href="styles/examples.css"/>
8
+ <link rel="stylesheet" href="../assets/chessboard.css"/>
9
+ <link rel="stylesheet" href="../assets/extensions/markers/markers.css"/>
10
+ <link rel="stylesheet" href="../assets/extensions/arrows/arrows.css"/>
11
+ <link rel="stylesheet" href="../assets/extensions/promotion-dialog/promotion-dialog.css"/>
12
+ </head>
13
+ <body>
14
+ <h1><a href="../">cm-chessboard</a></h1>
15
+ <h2>Example: Input enabled with move validation and promotion dialog</h2>
16
+ <p>Input enabled for white. <a href="https://github.com/jhlywa/chess.js">chess.js</a> does the validation and answers
17
+ with random moves.</p>
18
+ <div class="board board-large" id="board"></div>
19
+ <div id="output"></div>
20
+
21
+ <script type="module">
22
+ import {INPUT_EVENT_TYPE, COLOR, Chessboard, BORDER_TYPE} from "../src/Chessboard.js"
23
+ import {MARKER_TYPE, Markers} from "../src/extensions/markers/Markers.js"
24
+ import {PROMOTION_DIALOG_RESULT_TYPE, PromotionDialog} from "../src/extensions/promotion-dialog/PromotionDialog.js"
25
+ import {Accessibility} from "../src/extensions/accessibility/Accessibility.js"
26
+ import {Chess} from "https://cdn.jsdelivr.net/npm/chess.mjs@2/src/Chess.js"
27
+ import {RightClickAnnotator} from "../src/extensions/right-click-annotator/RightClickAnnotator.js"
28
+
29
+ const chess = new Chess("bnrqnkrb/pppppppp/8/8/8/8/PPPPPPPP/BNRQNKRB w KQkq - 0 1", {chess960: true})
30
+
31
+ let seed = 71
32
+
33
+ function random() {
34
+ const x = Math.sin(seed++) * 10000
35
+ return x - Math.floor(x)
36
+ }
37
+
38
+ function makeEngineMove(chessboard) {
39
+ const possibleMoves = chess.moves({verbose: true})
40
+ if (possibleMoves.length > 0) {
41
+ const randomIndex = Math.floor(random() * possibleMoves.length)
42
+ const randomMove = possibleMoves[randomIndex]
43
+ setTimeout(() => { // smoother with 500ms delay
44
+ chess.move({from: randomMove.from, to: randomMove.to})
45
+ chessboard.setPosition(chess.fen(), true)
46
+ chessboard.enableMoveInput(inputHandler, COLOR.white)
47
+ }, 500)
48
+ }
49
+ }
50
+
51
+ function inputHandler(event) {
52
+ console.log("inputHandler", event)
53
+ if (event.type === INPUT_EVENT_TYPE.movingOverSquare) {
54
+ return // ignore this event
55
+ }
56
+ if (event.type !== INPUT_EVENT_TYPE.moveInputFinished) {
57
+ event.chessboard.removeLegalMovesMarkers()
58
+ }
59
+ if (event.type === INPUT_EVENT_TYPE.moveInputStarted) {
60
+ // mark legal moves
61
+ const moves = chess.moves({square: event.squareFrom, verbose: true})
62
+ event.chessboard.addLegalMovesMarkers(moves)
63
+ return moves.length > 0
64
+ } else if (event.type === INPUT_EVENT_TYPE.validateMoveInput) {
65
+ const move = {from: event.squareFrom, to: event.squareTo, promotion: event.promotion}
66
+ const result = chess.move(move)
67
+ if (result) {
68
+ event.chessboard.state.moveInputProcess.then(() => { // wait for the move input process has finished
69
+ event.chessboard.setPosition(chess.fen(), true).then(() => { // update position, maybe castled and wait for animation has finished
70
+ makeEngineMove(event.chessboard)
71
+ })
72
+ })
73
+ } else {
74
+ // promotion?
75
+ let possibleMoves = chess.moves({square: event.squareFrom, verbose: true})
76
+ for (const possibleMove of possibleMoves) {
77
+ if (possibleMove.promotion && possibleMove.to === event.squareTo) {
78
+ event.chessboard.showPromotionDialog(event.squareTo, COLOR.white, (result) => {
79
+ console.log("promotion result", result)
80
+ if (result.type === PROMOTION_DIALOG_RESULT_TYPE.pieceSelected) {
81
+ chess.move({
82
+ from: event.squareFrom,
83
+ to: event.squareTo,
84
+ promotion: result.piece.charAt(1)
85
+ })
86
+ event.chessboard.setPosition(chess.fen(), true)
87
+ makeEngineMove(event.chessboard)
88
+ } else {
89
+ // promotion canceled
90
+ event.chessboard.enableMoveInput(inputHandler, COLOR.white)
91
+ event.chessboard.setPosition(chess.fen(), true)
92
+ }
93
+ })
94
+ return true
95
+ }
96
+ }
97
+ }
98
+ return result
99
+ } else if (event.type === INPUT_EVENT_TYPE.moveInputFinished) {
100
+ if (event.legalMove) {
101
+ event.chessboard.disableMoveInput()
102
+ }
103
+ }
104
+ }
105
+
106
+ const board = new Chessboard(document.getElementById("board"), {
107
+ position: chess.fen(),
108
+ assetsUrl: "../assets/",
109
+ style: {borderType: BORDER_TYPE.none, pieces: {file: "pieces/staunty.svg"}, animationDuration: 300},
110
+ orientation: COLOR.white,
111
+ extensions: [
112
+ {class: Markers, props: {autoMarkers: MARKER_TYPE.square}},
113
+ {class: RightClickAnnotator},
114
+ {class: PromotionDialog},
115
+ {class: Accessibility, props: {visuallyHidden: true}}
116
+ ]
117
+ })
118
+ board.enableMoveInput(inputHandler, COLOR.white)
119
+ </script>
120
+ </body>
121
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cm-chessboard",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "description": "A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.",
5
5
  "keywords": [
6
6
  "chess",
@@ -286,16 +286,20 @@ export class VisualMoveInput {
286
286
  const startPieceName = this.chessboard.getPiece(this.fromSquare)
287
287
  const startPieceColor = startPieceName ? startPieceName.substring(0, 1) : null
288
288
  if (color && startPieceColor === pieceColor) {
289
- this.moveInputCanceledCallback(this.fromSquare, square, MOVE_CANCELED_REASON.clickedAnotherPiece)
290
- if (this.moveInputStartedCallback(square)) {
291
- this.setMoveInputState(MOVE_INPUT_STATE.pieceClickedThreshold, {
292
- square: square,
293
- piece: pieceName,
294
- point: point,
295
- type: e.type
296
- })
297
- } else {
298
- this.setMoveInputState(MOVE_INPUT_STATE.reset)
289
+ // added to allow chess960 castling
290
+ const result = this.validateMoveInputCallback(this.fromSquare, square)
291
+ if(!result) {
292
+ this.moveInputCanceledCallback(this.fromSquare, square, MOVE_CANCELED_REASON.clickedAnotherPiece)
293
+ if (this.moveInputStartedCallback(square)) {
294
+ this.setMoveInputState(MOVE_INPUT_STATE.pieceClickedThreshold, {
295
+ square: square,
296
+ piece: pieceName,
297
+ point: point,
298
+ type: e.type
299
+ })
300
+ } else {
301
+ this.setMoveInputState(MOVE_INPUT_STATE.reset)
302
+ }
299
303
  }
300
304
  } else {
301
305
  this.setMoveInputState(MOVE_INPUT_STATE.moveDone, {square: square})