├── .gitignore ├── LICENSE ├── README.md ├── assets ├── _chessboard-theme.scss ├── chessboard.css ├── chessboard.css.map ├── chessboard.scss ├── extensions │ ├── arrows │ │ ├── arrows.css │ │ ├── arrows.css.map │ │ ├── arrows.scss │ │ ├── arrows.sketch │ │ └── arrows.svg │ ├── markers │ │ ├── markers.css │ │ ├── markers.css.map │ │ ├── markers.scss │ │ └── markers.svg │ └── promotion-dialog │ │ ├── promotion-dialog.css │ │ ├── promotion-dialog.css.map │ │ └── promotion-dialog.scss └── pieces │ ├── standard.svg │ └── staunty.svg ├── examples ├── destroy-many-boards.html ├── different-styles.html ├── enable-input.html ├── extensions │ ├── accessibility-extension.html │ ├── arrows-extension.html │ ├── html-layer-extension.html │ ├── markers-extension.html │ ├── persistence-extension.html │ ├── promotion-dialog-extension.html │ └── right-click-annotator.html ├── many-boards.html ├── pieces-animation.html ├── pointer-events.html ├── responsive-board.html ├── sandbox │ └── arrows-extension-multiple.html ├── simple-boards.html ├── styles │ ├── examples.css │ ├── examples.css.map │ └── examples.scss └── validate-moves.html ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── src ├── Chessboard.js ├── extensions │ ├── accessibility │ │ ├── Accessibility.js │ │ └── I18n.js │ ├── arrows │ │ └── Arrows.js │ ├── auto-border-none │ │ └── AutoBorderNone.js │ ├── html-layer │ │ └── HtmlLayer.js │ ├── markers │ │ ├── Markers.js │ │ └── README.md │ ├── persistence │ │ └── Persistence.js │ ├── promotion-dialog │ │ └── PromotionDialog.js │ └── right-click-annotator │ │ └── RightClickAnnotator.js ├── lib │ ├── Svg.js │ └── Utils.js ├── model │ ├── ChessboardState.js │ ├── Extension.js │ └── Position.js └── view │ ├── ChessboardView.js │ ├── PositionAnimationsQueue.js │ └── VisualMoveInput.js └── test ├── TestChessboard.js ├── TestMarkers.js ├── TestPiecesAnimation.js ├── TestPosition.js ├── index.html └── mocks └── ViewMock.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea 3 | /node_modules 4 | /lib 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Stefan Haack (http://shaack.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cm-chessboard 2 | 3 | A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and **without dependencies**. 4 | 5 | cm-chessboard is the main chessboard of 6 | [chessmail.eu](https://www.chessmail.eu) and [chessmail.de](https://www.chessmail.de). It is also used 7 | in [chess-console](https://shaack.com/projekte/chess-console/examples/load-pgn.html) and in 8 | [cm-fen-editor](https://shaack.com/projekte/cm-fen-editor/). They are all nice written ES6 Modules to handle different aspects of chess games. 9 | 10 | ## Features 11 | 12 | - **No dependencies**, just clean ES6 13 | - [Can handle moves input via click or drag](https://shaack.com/projekte/cm-chessboard/examples/validate-moves.html) 14 | - [Styleable via css and supports multiple piece sets](https://shaack.com/projekte/cm-chessboard/examples/different-styles.html) 15 | - Uses SVG for rendering 16 | - [Allows adding extensions to extend the 17 | functionality](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html) 18 | 19 | ## Extensions 20 | 21 | The core of cm-chessboard is small, fast and reduced to the essentials. You can easily extend its functionality with extensions. 22 | 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). Adds the handling of mouse events to draw them on the board. 24 | - [Markers Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) ⇨ create markers on specific squares 25 | - [Arrows Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html) ⇨ renders arrows on the chessboard 26 | - [Accessibility Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/accessibility-extension.html) ⇨ makes the chessboard more accessible 27 | - [PromotionDialog Extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/promotion-dialog-extension.html) ⇨ shows a dialog to select the piece to promote to 28 | 29 | ## Demo and repository 30 | 31 | - **Demo: [http://shaack.com/projekte/cm-chessboard/](https://shaack.com/projekte/cm-chessboard/)** 32 | - **Repository: [https://github.com/shaack/cm-chessboard](https://github.com/shaack/cm-chessboard)** 33 | 34 | ![Example chessboards](https://shaack.com/projekte/assets/img/example_chessboards_staunty.png?v=2) 35 | 36 | ## Installation and first steps 37 | 38 | ### Step 1: Install the package 39 | 40 | - **Option 1:** Install the [npm package](https://www.npmjs.com/package/cm-chessboard) with `npm install cm-chessboard`. 41 | - **Option 2:** Download the code from [GitHub](https://github.com/shaack/cm-chessboard). 42 | - **Option 3:** Use it via CDN https://cdn.jsdelivr.net/npm/cm-chessboard@8/src/Chessboard.js 43 | 44 | ### Step 2: Create your cm-chessboard page 45 | 46 | #### Step 2a: Include the CSS file 47 | 48 | ```html 49 | 50 | ``` 51 | 52 | - Some extensions, like "[Markers](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html)", "[Promotion Dialog](https://shaack.com/projekte/cm-chessboard/examples/extensions/promotion-dialog-extension.html)" or "[Arrows](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html)" need additional CSS. See the examples. 53 | 54 | #### Step 2b: Create a container for the chessboard 55 | 56 | ```html 57 |
58 | ``` 59 | 60 | #### Step 2c: Create the chessboard in your JavaScript code. 61 | 62 | ```html 63 | 64 | 72 | ``` 73 | 74 | You need to configure the `assetsUrl` in your chessboard props (the second parameter). The `assetsUrl` must be the path to the `assets` folder of this project, where the pieces SVGs and other resources are located. 75 | 76 | You can also copy the `assets` folder from `cm-chessboard/assets` to your project and modify the content. 77 | 78 | #### See also 79 | 80 | - [Simple cm-chessboard example online](https://shaack.com/projekte/cm-chessboard/examples/simple-boards.html) 81 | 82 | ### Step 3: (Optional) Enable user input 83 | 84 | To enable the user to move the pieces, you have to enable the move input. 85 | 86 | ```javascript 87 | const board = new Chessboard(document.getElementById("board"), { 88 | position: FEN.start, 89 | assetsUrl: "../assets/", 90 | extensions: [{class: Markers}] // Looks better with markers. (Don't forget to also include the CSS for the markers) 91 | }) 92 | 93 | board.enableMoveInput(inputHandler) // This enables the move input 94 | 95 | function inputHandler(event) { 96 | console.log(event) 97 | if(event.type === INPUT_EVENT_TYPE.moveInputStarted || 98 | event.type === INPUT_EVENT_TYPE.validateMoveInput) { 99 | return true // false cancels move 100 | } 101 | } 102 | ``` 103 | 104 | #### See also 105 | 106 | - [Simple example with move input enabled](https://shaack.com/projekte/cm-chessboard/examples/enable-input.html) 107 | - [More complex example with move validation](https://shaack.com/projekte/cm-chessboard/examples/validate-moves.html) 108 | 109 | Take a look at the [/examples](https://github.com/shaack/cm-chessboard/tree/master/examples) folder for more examples. 110 | 111 | ## Configuration 112 | 113 | Below is the default configuration 114 | 115 | ```javascript 116 | this.props = { 117 | position: FEN.empty, // set position as fen, use FEN.start or FEN.empty as shortcuts 118 | orientation: COLOR.white, // white on bottom 119 | responsive: true, // resize the board automatically to the size of the context element 120 | assetsUrl: "./assets/", // put all css and sprites in this folder, will be ignored for absolute urls of assets files 121 | assetsCache: true, // cache the sprites, deactivate if you want to use multiple pieces sets in one page 122 | style: { 123 | cssClass: "default", // set the css theme of the board, try "green", "blue" or "chess-club" 124 | showCoordinates: true, // show ranks and files 125 | borderType: BORDER_TYPE.none, // "thin" thin border, "frame" wide border with coordinates in it, "none" no border 126 | aspectRatio: 1, // height/width of the board 127 | pieces: { 128 | type: PIECES_FILE_TYPE.svgSprite, // pieces are in an SVG sprite, no other type supported for now 129 | file: "pieces/standard.svg", // the filename of the sprite in `assets/pieces/` or an absolute url like `https://…` or `/…` 130 | tileSize: 40 // the tile size in the sprite 131 | }, 132 | animationDuration: 300 // pieces animation duration in milliseconds. Disable all animations with `0` 133 | }, 134 | extensions: [ /* {class: ExtensionClass, props: { ... }} */] // add extensions here 135 | } 136 | ``` 137 | 138 | ## API 139 | 140 | ### constructor 141 | 142 | `new Chessboard(context, props = {})` 143 | 144 | - **`context`**: the HTML DOM element being the container of the widget 145 | - **`props`**: The board configuration (properties) 146 | 147 | ### setPiece(square, piece, animated = false) 148 | 149 | Sets a piece on a square. Example: `board.setPiece("e4", PIECE.blackKnight, true)` or 150 | `board.setPiece("e4", "bn")`. Remove a Piece with `board.setPiece("e4", null)`. Returns a **Promise**, which is 151 | resolved, 152 | after the animation finished. 153 | 154 | ### getPiece(square) 155 | 156 | Returns the piece on a square or `null` if the square is empty. 157 | 158 | ### movePiece(squareFrom, squareTo, animated = false) 159 | 160 | Move a piece from `squareFrom` to `squareTo`. Returns a **Promise**, which is resolved, after the animation finished. 161 | 162 | [Example for **movePiece**](https://shaack.com/projekte/cm-chessboard/examples/pieces-animation.html) 163 | 164 | ### setPosition(fen, animated = false) 165 | 166 | Sets the position as `fen` or only the position part of a `fen`. Returns a **Promise**, which is resolved, after the animation finished. 167 | 168 | [Example for **setPosition**](https://shaack.com/projekte/cm-chessboard/examples/pieces-animation.html) 169 | 170 | ### getPosition() 171 | 172 | Returns the board position in form of the position part of a `fen`. 173 | 174 | ### setOrientation(color) 175 | 176 | Sets the board orientation (color at bottom). Allowed values are `COLOR.white` or `COLOR.black`. 177 | 178 | [Example for **setOrientation**](https://shaack.com/projekte/cm-chessboard/examples/enable-input.html) 179 | 180 | ### getOrientation() 181 | 182 | Returns the board orientation. 183 | 184 | ### destroy() 185 | 186 | Removes the board from the DOM. 187 | 188 | [Example for **destroy**](https://shaack.com/projekte/cm-chessboard/examples/destroy-many-boards.html) 189 | 190 | ### enableMoveInput(eventHandler, color = undefined) 191 | 192 | Enables moves via user input (mouse or touch). Set optional `color`, if you want to enable the move input for a specific 193 | side, `COLOR.white` or `COLOR.black`. 194 | 195 | `eventHandler` is called on specific events of the user interaction. Receives the parameter `event`. 196 | 197 | ```javascript 198 | board.enableMoveInput((event) => { 199 | // handle user input here 200 | }, COLOR.white) 201 | ``` 202 | 203 | [Example for **enableMoveInput**](http://shaack.com/projekte/cm-chessboard/examples/enable-input.html) 204 | 205 | The event has the following **`event.type`**: 206 | 207 | - **`INPUT_EVENT_TYPE.moveInputStarted`**: User started the move input, `event.squareFrom` contains the coordinates. 208 | Return `true` or `false` to validate the start square. `false` cancels the move. 209 | - **`INPUT_EVENT_TYPE.validateMoveInput`**: To validate the users move input. `event.squareFrom` and `event.squareTo` 210 | contain the coordinates. Return `true` or `false` to validate the move. `false` cancels the move. 211 | - **`INPUT_EVENT_TYPE.moveInputCanceled`**: The user canceled the move with clicking again on the start square, clicking 212 | outside the board or right click. 213 | - **`INPUT_EVENT_TYPE.moveInputFinished`**: Fired after the move was made, also when canceled. 214 | - **`INPUT_EVENT_TYPE.movingOverSquare`**: Fired, when the user moves the piece over a square. `event.squareTo` contains 215 | the coordinates. 216 | 217 | ```javascript 218 | chessboard.enableMoveInput((event) => { 219 | console.log("move input", event) 220 | switch (event.type) { 221 | case INPUT_EVENT_TYPE.moveInputStarted: 222 | console.log(`moveInputStarted: ${event.squareFrom}`) 223 | return true // false cancels move 224 | case INPUT_EVENT_TYPE.validateMoveInput: 225 | console.log(`validateMoveInput: ${event.squareFrom}-${event.squareTo}`) 226 | return true // false cancels move 227 | case INPUT_EVENT_TYPE.moveInputCanceled: 228 | console.log(`moveInputCanceled`) 229 | break 230 | case INPUT_EVENT_TYPE.moveInputFinished: 231 | console.log(`moveInputFinished`) 232 | break 233 | case INPUT_EVENT_TYPE.movingOverSquare: 234 | console.log(`movingOverSquare: ${event.squareTo}`) 235 | break 236 | } 237 | }, COLOR.white) 238 | ``` 239 | 240 | ### disableMoveInput() 241 | 242 | Disables moves via user input. 243 | 244 | ## Piece sets 245 | 246 | cm-chessboard supports alternative piece sets. A piece set is defined in an SVG sprite. cm-chessboard is shipped with 247 | two sets, the default [staunty](https://github.com/ornicar/lila/tree/master/public/piece/staunty) ( 248 | chessboard-sprite-staunty.svg) and a sprite of the 249 | [Wikimedia standard pieces](https://commons.wikimedia.org/wiki/Category:SVG_chess_pieces/Standard) 250 | (chessboard-sprite.svg). 251 | 252 | Sprites must be 40x40px in size where the piece elements must have ids like 253 | "bp" (black pawn) or "wq" (white queen). Just open the sprite in a text editor, SVG is readable like HTML. 254 | 255 | ## Extensions 256 | 257 | cm-chessboard provides the ability to extend its functionality with extensions. Extensions extend the class `Extension` 258 | and have access to the chessboard and can register extension points. 259 | 260 | ### registerExtensionPoint(name, callback) 261 | 262 | ```js 263 | class MyCoolChessboardExtension extends Extension { 264 | constructor(chessboard, props) { 265 | super(chessboard, props) 266 | this.registerExtensionPoint(EXTENSION_POINT.moveInput, (data) => { 267 | // do something on move [start | cancel | done] 268 | console.log(data) 269 | }) 270 | } 271 | } 272 | ``` 273 | 274 | Currently possible extension points are defined in `Extension.js`. 275 | 276 | ```js 277 | export const EXTENSION_POINT = { 278 | positionChanged: "positionChanged", // the positions of the pieces was changed 279 | boardChanged: "boardChanged", // the board (orientation) was changed 280 | boardResized: "boardResized", // the board was resized 281 | moveInputToggled: "moveInputToggled", // move input was enabled or disabled 282 | moveInput: "moveInput", // move started, moving over a square, validating or canceled 283 | beforeRedrawBoard: "beforeRedrawBoard", // called before redrawing the board 284 | afterRedrawBoard: "afterRedrawBoard", // called after redrawing the board 285 | animation: "animation", // called on animation start, end and on every animation frame 286 | destroy: "destroy" // called, before the board is destroyed 287 | } 288 | ``` 289 | 290 | Enable extensions via the chessboard props. 291 | 292 | ```js 293 | const chessboard = new Chessboard(document.getElementById("board"), { 294 | position: FEN.start, 295 | extensions: // list of used extensions 296 | [{ 297 | class: MyCoolChessboardExtension, // the class of the extension 298 | props: { 299 | // configure the extension here 300 | } 301 | }] 302 | }) 303 | ``` 304 | 305 | ### Add methods to the chessboard 306 | 307 | Add methods to the chessboard in the constructor of your extension like shown below. 308 | 309 | ```js 310 | chessboard.addMarker = this.addMarker.bind(this) 311 | ``` 312 | 313 | ## The main extensions contained in cm-chessboard 314 | 315 | ### Markers extension 316 | 317 | Creates markers on the board. Example: [Markers extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) 318 | 319 | See the [README](src/extensions/markers/README.md) of the Markers. extension. 320 | 321 | ### Arrows extension 322 | 323 | Draw arrows on the board. Example: [Arrows extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/arrows-extension.html) 324 | 325 | #### Methods 326 | 327 | ##### addArrow(type, fromSquare, toSquare) 328 | 329 | Add an arrow. 330 | 331 | ##### removeArrows(type, from, to) 332 | 333 | To remove all arrows, call `chessboard.removeArrows()` without parameters. To remove all arrows of a specific 334 | type (type "danger"), call `chessboard.removeArrows(ARROW_TYPE.danger)`. To remove all arrows starting at " 335 | e2" 336 | you can call `chessboard.removeArrows(undefined, "e2")` and so on... 337 | 338 | ##### getArrows(type, from, to) 339 | 340 | To get all arrows, call `chessboard.getArrows()` without parameters, as with `removeArrows(type, from, to)`. 341 | 342 | ### Accessibility Extension 343 | 344 | This extension ensures that visual impaired people can better use the chessboard. It displays the braille notation 345 | of the current position in the alt tag of the board image and enables a form to move the pieces via text input. It 346 | can also display the board as HTML table and the pieces as list. 347 | 348 | See the example [Accessibility extension](https://shaack.com/projekte/cm-chessboard/examples/extensions/accessibility-extension.html) 349 | 350 | #### Usage 351 | 352 | ```js 353 | const chessboard = new Chessboard(document.getElementById("board"), { 354 | position: FEN.start, 355 | sprite: {url: "../assets/images/chessboard-sprite.svg"}, 356 | // animationDuration: 0, // optional, set to 0 to disable animations 357 | style: { 358 | cssClass: "default-contrast" // make the coordinates better visible with the "default-contrast" theme 359 | }, 360 | extensions: 361 | [{ 362 | class: Accessibility, 363 | props: { 364 | brailleNotationInAlt: true, // show the braille notation of the position in the alt attribute of the SVG image 365 | boardAsTable: true, // display the board additionally as HTML table 366 | movePieceForm: true, // display a form to move a piece (from, to, move) 367 | piecesAsList: true, // display the pieces additionally as List 368 | visuallyHidden: false // hide all those extra outputs visually but keep them accessible for screen readers and braille displays 369 | } 370 | 371 | }] 372 | }) 373 | ``` 374 | 375 | 376 | ## Usage with JS Frameworks 377 | 378 | - Works with **Vue** out of the box 379 | - Works with **Svelte** out of the box 380 | - I don't use **React**, but there exists a ticket from someone who is using cm-chessboard with 381 | react: https://github.com/shaack/cm-chessboard/issues/20 382 | - It should work also with **all other JS frameworks**, because cm-chessboard is written in standard ES6 and has **no 383 | dependencies**. 384 | 385 | ## Licenses 386 | 387 | - License for the code: [MIT](https://github.com/shaack/cm-chessboard/blob/master/LICENSE) 388 | - License for the Staunty SVG-pieces ( 389 | chessboard-sprite-staunty.svg): [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 390 | - License for the Wikimedia SVG-pieces ( 391 | chessboard-sprite.svg): [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) 392 | 393 | --- 394 | 395 | Find more high quality JavaScript modules from [shaack.com](https://shaack.com) 396 | on [our projects page](https://shaack.com/works). 397 | -------------------------------------------------------------------------------- /assets/_chessboard-theme.scss: -------------------------------------------------------------------------------- 1 | $darkenCoordinates: 8%; 2 | 3 | @mixin cm-chessboard-theme($name, 4 | $light, $dark, 5 | $coordinates-light: mix(black, $dark, $darkenCoordinates), 6 | $coordinates-dark: mix(white, $light, $darkenCoordinates), 7 | $coordinates-frame: mix(black, $dark, $darkenCoordinates), 8 | $border: $dark, $frame-bg: $light) { 9 | .cm-chessboard.#{$name} { 10 | .board { 11 | .square { 12 | &.white { 13 | fill: $light; 14 | } 15 | 16 | &.black { 17 | fill: $dark; 18 | } 19 | } 20 | } 21 | // border, with borderType "thin" 22 | &.border-type-thin { 23 | .board { 24 | .border { 25 | stroke: $border; 26 | stroke-width: 0.7%; 27 | fill: $dark; 28 | } 29 | } 30 | } 31 | &.border-type-none { 32 | .board { 33 | .border { 34 | stroke: $border; 35 | stroke-width: 0; 36 | fill: $dark; 37 | } 38 | } 39 | } 40 | // border, with borderType "frame" 41 | &.border-type-frame { 42 | .board { 43 | .border { 44 | fill: $frame-bg; 45 | stroke: none; 46 | } 47 | .border-inner { 48 | fill: $dark; 49 | stroke: $border; 50 | stroke-width: 0.7%; 51 | } 52 | } 53 | } 54 | .coordinates { 55 | pointer-events: none; 56 | user-select: none; 57 | 58 | .coordinate { 59 | fill: $coordinates-frame; 60 | font-size: 7px; 61 | cursor: default; 62 | &.black { 63 | fill: $coordinates-dark; 64 | } 65 | &.white { 66 | fill: $coordinates-light; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /assets/chessboard.css: -------------------------------------------------------------------------------- 1 | .cm-chessboard .board.input-enabled .square { 2 | cursor: pointer; } 3 | 4 | .cm-chessboard .coordinates, .cm-chessboard .markers-layer, .cm-chessboard .pieces-layer, .cm-chessboard .markers-top-layer { 5 | pointer-events: none; } 6 | 7 | .cm-chessboard-content .list-inline { 8 | padding-left: 0; 9 | list-style: none; } 10 | 11 | .cm-chessboard-content .list-inline-item { 12 | display: inline-block; } 13 | .cm-chessboard-content .list-inline-item:not(:last-child) { 14 | margin-right: 1rem; } 15 | 16 | .cm-chessboard-content .list-inline { 17 | padding-left: 0; 18 | list-style: none; } 19 | 20 | .cm-chessboard-content .list-inline-item { 21 | display: inline-block; } 22 | .cm-chessboard-content .list-inline-item:not(:last-child) { 23 | margin-right: 1rem; } 24 | 25 | .cm-chessboard-accessibility.visually-hidden { 26 | width: 1px; 27 | height: 1px; 28 | padding: 0; 29 | margin: -1px; 30 | overflow: hidden; 31 | clip: rect(0, 0, 0, 0); 32 | white-space: nowrap; 33 | border: 0; } 34 | 35 | .cm-chessboard.default .board .square.white { 36 | fill: #ecdab9; } 37 | 38 | .cm-chessboard.default .board .square.black { 39 | fill: #c5a076; } 40 | 41 | .cm-chessboard.default.border-type-thin .board .border { 42 | stroke: #c5a076; 43 | stroke-width: 0.7%; 44 | fill: #c5a076; } 45 | 46 | .cm-chessboard.default.border-type-none .board .border { 47 | stroke: #c5a076; 48 | stroke-width: 0; 49 | fill: #c5a076; } 50 | 51 | .cm-chessboard.default.border-type-frame .board .border { 52 | fill: #ecdab9; 53 | stroke: none; } 54 | 55 | .cm-chessboard.default.border-type-frame .board .border-inner { 56 | fill: #c5a076; 57 | stroke: #c5a076; 58 | stroke-width: 0.7%; } 59 | 60 | .cm-chessboard.default .coordinates { 61 | pointer-events: none; 62 | user-select: none; } 63 | .cm-chessboard.default .coordinates .coordinate { 64 | fill: #b5936d; 65 | font-size: 7px; 66 | cursor: default; } 67 | .cm-chessboard.default .coordinates .coordinate.black { 68 | fill: #eeddbf; } 69 | .cm-chessboard.default .coordinates .coordinate.white { 70 | fill: #b5936d; } 71 | 72 | .cm-chessboard.default-contrast .board .square.white { 73 | fill: #ecdab9; } 74 | 75 | .cm-chessboard.default-contrast .board .square.black { 76 | fill: #c5a076; } 77 | 78 | .cm-chessboard.default-contrast.border-type-thin .board .border { 79 | stroke: #c5a076; 80 | stroke-width: 0.7%; 81 | fill: #c5a076; } 82 | 83 | .cm-chessboard.default-contrast.border-type-none .board .border { 84 | stroke: #c5a076; 85 | stroke-width: 0; 86 | fill: #c5a076; } 87 | 88 | .cm-chessboard.default-contrast.border-type-frame .board .border { 89 | fill: #ecdab9; 90 | stroke: none; } 91 | 92 | .cm-chessboard.default-contrast.border-type-frame .board .border-inner { 93 | fill: #c5a076; 94 | stroke: #c5a076; 95 | stroke-width: 0.7%; } 96 | 97 | .cm-chessboard.default-contrast .coordinates { 98 | pointer-events: none; 99 | user-select: none; } 100 | .cm-chessboard.default-contrast .coordinates .coordinate { 101 | fill: #b5936d; 102 | font-size: 7px; 103 | cursor: default; } 104 | .cm-chessboard.default-contrast .coordinates .coordinate.black { 105 | fill: #333; } 106 | .cm-chessboard.default-contrast .coordinates .coordinate.white { 107 | fill: #333; } 108 | 109 | .cm-chessboard.green .board .square.white { 110 | fill: #E0DDCC; } 111 | 112 | .cm-chessboard.green .board .square.black { 113 | fill: #4c946a; } 114 | 115 | .cm-chessboard.green.border-type-thin .board .border { 116 | stroke: #4c946a; 117 | stroke-width: 0.7%; 118 | fill: #4c946a; } 119 | 120 | .cm-chessboard.green.border-type-none .board .border { 121 | stroke: #4c946a; 122 | stroke-width: 0; 123 | fill: #4c946a; } 124 | 125 | .cm-chessboard.green.border-type-frame .board .border { 126 | fill: #E0DDCC; 127 | stroke: none; } 128 | 129 | .cm-chessboard.green.border-type-frame .board .border-inner { 130 | fill: #4c946a; 131 | stroke: #4c946a; 132 | stroke-width: 0.7%; } 133 | 134 | .cm-chessboard.green .coordinates { 135 | pointer-events: none; 136 | user-select: none; } 137 | .cm-chessboard.green .coordinates .coordinate { 138 | fill: #468862; 139 | font-size: 7px; 140 | cursor: default; } 141 | .cm-chessboard.green .coordinates .coordinate.black { 142 | fill: #e2e0d0; } 143 | .cm-chessboard.green .coordinates .coordinate.white { 144 | fill: #468862; } 145 | 146 | .cm-chessboard.blue .board .square.white { 147 | fill: #d8ecfb; } 148 | 149 | .cm-chessboard.blue .board .square.black { 150 | fill: #86afcf; } 151 | 152 | .cm-chessboard.blue.border-type-thin .board .border { 153 | stroke: #86afcf; 154 | stroke-width: 0.7%; 155 | fill: #86afcf; } 156 | 157 | .cm-chessboard.blue.border-type-none .board .border { 158 | stroke: #86afcf; 159 | stroke-width: 0; 160 | fill: #86afcf; } 161 | 162 | .cm-chessboard.blue.border-type-frame .board .border { 163 | fill: #d8ecfb; 164 | stroke: none; } 165 | 166 | .cm-chessboard.blue.border-type-frame .board .border-inner { 167 | fill: #86afcf; 168 | stroke: #86afcf; 169 | stroke-width: 0.7%; } 170 | 171 | .cm-chessboard.blue .coordinates { 172 | pointer-events: none; 173 | user-select: none; } 174 | .cm-chessboard.blue .coordinates .coordinate { 175 | fill: #7ba1be; 176 | font-size: 7px; 177 | cursor: default; } 178 | .cm-chessboard.blue .coordinates .coordinate.black { 179 | fill: #dbeefb; } 180 | .cm-chessboard.blue .coordinates .coordinate.white { 181 | fill: #7ba1be; } 182 | 183 | .cm-chessboard.chess-club .board .square.white { 184 | fill: #E6D3B1; } 185 | 186 | .cm-chessboard.chess-club .board .square.black { 187 | fill: #AF6B3F; } 188 | 189 | .cm-chessboard.chess-club.border-type-thin .board .border { 190 | stroke: #692e2b; 191 | stroke-width: 0.7%; 192 | fill: #AF6B3F; } 193 | 194 | .cm-chessboard.chess-club.border-type-none .board .border { 195 | stroke: #692e2b; 196 | stroke-width: 0; 197 | fill: #AF6B3F; } 198 | 199 | .cm-chessboard.chess-club.border-type-frame .board .border { 200 | fill: #692e2b; 201 | stroke: none; } 202 | 203 | .cm-chessboard.chess-club.border-type-frame .board .border-inner { 204 | fill: #AF6B3F; 205 | stroke: #692e2b; 206 | stroke-width: 0.7%; } 207 | 208 | .cm-chessboard.chess-club .coordinates { 209 | pointer-events: none; 210 | user-select: none; } 211 | .cm-chessboard.chess-club .coordinates .coordinate { 212 | fill: #E6D3B1; 213 | font-size: 7px; 214 | cursor: default; } 215 | .cm-chessboard.chess-club .coordinates .coordinate.black { 216 | fill: #E6D3B1; } 217 | .cm-chessboard.chess-club .coordinates .coordinate.white { 218 | fill: #AF6B3F; } 219 | 220 | .cm-chessboard.chessboard-js .board .square.white { 221 | fill: #f0d9b5; } 222 | 223 | .cm-chessboard.chessboard-js .board .square.black { 224 | fill: #b58863; } 225 | 226 | .cm-chessboard.chessboard-js.border-type-thin .board .border { 227 | stroke: #404040; 228 | stroke-width: 0.7%; 229 | fill: #b58863; } 230 | 231 | .cm-chessboard.chessboard-js.border-type-none .board .border { 232 | stroke: #404040; 233 | stroke-width: 0; 234 | fill: #b58863; } 235 | 236 | .cm-chessboard.chessboard-js.border-type-frame .board .border { 237 | fill: #f0d9b5; 238 | stroke: none; } 239 | 240 | .cm-chessboard.chessboard-js.border-type-frame .board .border-inner { 241 | fill: #b58863; 242 | stroke: #404040; 243 | stroke-width: 0.7%; } 244 | 245 | .cm-chessboard.chessboard-js .coordinates { 246 | pointer-events: none; 247 | user-select: none; } 248 | .cm-chessboard.chessboard-js .coordinates .coordinate { 249 | fill: #404040; 250 | font-size: 7px; 251 | cursor: default; } 252 | .cm-chessboard.chessboard-js .coordinates .coordinate.black { 253 | fill: #f0d9b5; } 254 | .cm-chessboard.chessboard-js .coordinates .coordinate.white { 255 | fill: #b58863; } 256 | 257 | .cm-chessboard.black-and-white .board .square.white { 258 | fill: #ffffff; } 259 | 260 | .cm-chessboard.black-and-white .board .square.black { 261 | fill: #9c9c9c; } 262 | 263 | .cm-chessboard.black-and-white.border-type-thin .board .border { 264 | stroke: #9c9c9c; 265 | stroke-width: 0.7%; 266 | fill: #9c9c9c; } 267 | 268 | .cm-chessboard.black-and-white.border-type-none .board .border { 269 | stroke: #9c9c9c; 270 | stroke-width: 0; 271 | fill: #9c9c9c; } 272 | 273 | .cm-chessboard.black-and-white.border-type-frame .board .border { 274 | fill: #ffffff; 275 | stroke: none; } 276 | 277 | .cm-chessboard.black-and-white.border-type-frame .board .border-inner { 278 | fill: #9c9c9c; 279 | stroke: #9c9c9c; 280 | stroke-width: 0.7%; } 281 | 282 | .cm-chessboard.black-and-white .coordinates { 283 | pointer-events: none; 284 | user-select: none; } 285 | .cm-chessboard.black-and-white .coordinates .coordinate { 286 | fill: #909090; 287 | font-size: 7px; 288 | cursor: default; } 289 | .cm-chessboard.black-and-white .coordinates .coordinate.black { 290 | fill: white; } 291 | .cm-chessboard.black-and-white .coordinates .coordinate.white { 292 | fill: #909090; } 293 | 294 | /*# sourceMappingURL=chessboard.css.map */ -------------------------------------------------------------------------------- /assets/chessboard.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "chessboard.css", 4 | "sources": [ 5 | "chessboard.scss", 6 | "_chessboard-theme.scss" 7 | ], 8 | "names": [], 9 | "mappings": "AAGA,AAIM,cAJQ,CAEZ,MAAM,AACH,cAAc,CACb,OAAO,CAAC;EACN,MAAM,EAAE,OAAO,GAChB;;AANP,AAUE,cAVY,CAUZ,YAAY,EAVd,cAAc,CAUE,cAAc,EAV9B,cAAc,CAUkB,aAAa,EAV7C,cAAc,CAUiC,kBAAkB,CAAC;EAC9D,cAAc,EAAE,IAAI,GACrB;;AAGH,AACE,sBADoB,CACpB,YAAY,CAAC;EACX,YAAY,EAAE,CAAC;EACf,UAAU,EAAE,IAAI,GACjB;;AAJH,AAME,sBANoB,CAMpB,iBAAiB,CAAC;EAChB,OAAO,EAAE,YAAY,GAKtB;EAZH,AASI,sBATkB,CAMpB,iBAAiB,CAGd,GAAK,EAAC,UAAU,EAAE;IACjB,YAAY,EAAE,IAAI,GACnB;;AAIL,AAEE,sBAFoB,CAEpB,YAAY,CAAC;EACX,YAAY,EAAE,CAAC;EACf,UAAU,EAAE,IAAI,GACjB;;AALH,AAOE,sBAPoB,CAOpB,iBAAiB,CAAC;EAChB,OAAO,EAAE,YAAY,GAKtB;EAbH,AAUI,sBAVkB,CAOpB,iBAAiB,CAGd,GAAK,EAAC,UAAU,EAAE;IACjB,YAAY,EAAE,IAAI,GACnB;;AAIL,AAAA,4BAA4B,AAAA,gBAAgB,CAAC;EAC3C,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,GAAG;EACX,OAAO,EAAE,CAAC;EACV,MAAM,EAAE,IAAI;EACZ,QAAQ,EAAE,MAAM;EAChB,IAAI,EAAE,gBAAgB;EACtB,WAAW,EAAE,MAAM;EACnB,MAAM,EAAE,CAAC,GACV;;AC1DD,AAWQ,cAXM,AAAA,QAAQ,CASlB,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,EDmDE,OAAO,GClDd;;AAbT,AAeQ,cAfM,AAAA,QAAQ,CASlB,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,ED+CW,OAAO,GC9CvB;;AAjBT,AAuBQ,cAvBM,AAAA,QAAQ,AAqBjB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDuCS,OAAO;ECtCtB,YAAY,EAAE,IAAI;EAClB,IAAI,EDqCW,OAAO,GCpCvB;;AA3BT,AAgCQ,cAhCM,AAAA,QAAQ,AA8BjB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,ED8BS,OAAO;EC7BtB,YAAY,EAAE,CAAC;EACf,IAAI,ED4BW,OAAO,GC3BvB;;AApCT,AA0CQ,cA1CM,AAAA,QAAQ,AAwCjB,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,EDoBE,OAAO;ECnBb,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,QAAQ,AAwCjB,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,EDgBW,OAAO;ECftB,MAAM,EDeS,OAAO;ECdtB,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,QAAQ,CAqDlB,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,QAAQ,CAqDlB,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EApDS,OAAqC;IAqDlD,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,QAAQ,CAqDlB,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EAzDK,OAAsC,GA0DhD;IA/DT,AAgEQ,cAhEM,AAAA,QAAQ,CAqDlB,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EA7DM,OAAqC,GA8DhD;;AAlET,AAWQ,cAXM,AAAA,iBAAiB,CAS3B,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,EDuDE,OAAO,GCtDd;;AAbT,AAeQ,cAfM,AAAA,iBAAiB,CAS3B,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,EDmDW,OAAO,GClDvB;;AAjBT,AAuBQ,cAvBM,AAAA,iBAAiB,AAqB1B,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,ED2CS,OAAO;EC1CtB,YAAY,EAAE,IAAI;EAClB,IAAI,EDyCW,OAAO,GCxCvB;;AA3BT,AAgCQ,cAhCM,AAAA,iBAAiB,AA8B1B,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDkCS,OAAO;ECjCtB,YAAY,EAAE,CAAC;EACf,IAAI,EDgCW,OAAO,GC/BvB;;AApCT,AA0CQ,cA1CM,AAAA,iBAAiB,AAwC1B,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,EDwBE,OAAO;ECvBb,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,iBAAiB,AAwC1B,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,EDoBW,OAAO;ECnBtB,MAAM,EDmBS,OAAO;EClBtB,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,iBAAiB,CAqD3B,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,iBAAiB,CAqD3B,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EApDS,OAAqC;IAqDlD,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,iBAAiB,CAqD3B,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EDMQ,IAAI,GCLjB;IA/DT,AAgEQ,cAhEM,AAAA,iBAAiB,CAqD3B,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EDGE,IAAI,GCFX;;AAlET,AAWQ,cAXM,AAAA,MAAM,CAShB,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,ED4DE,OAAO,GC3Dd;;AAbT,AAeQ,cAfM,AAAA,MAAM,CAShB,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,EDwDW,OAAO,GCvDvB;;AAjBT,AAuBQ,cAvBM,AAAA,MAAM,AAqBf,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDgDS,OAAO;EC/CtB,YAAY,EAAE,IAAI;EAClB,IAAI,ED8CW,OAAO,GC7CvB;;AA3BT,AAgCQ,cAhCM,AAAA,MAAM,AA8Bf,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDuCS,OAAO;ECtCtB,YAAY,EAAE,CAAC;EACf,IAAI,EDqCW,OAAO,GCpCvB;;AApCT,AA0CQ,cA1CM,AAAA,MAAM,AAwCf,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,ED6BE,OAAO;EC5Bb,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,MAAM,AAwCf,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,EDyBW,OAAO;ECxBtB,MAAM,EDwBS,OAAO;ECvBtB,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,MAAM,CAqDhB,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,MAAM,CAqDhB,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EApDS,OAAqC;IAqDlD,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,MAAM,CAqDhB,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EAzDK,OAAsC,GA0DhD;IA/DT,AAgEQ,cAhEM,AAAA,MAAM,CAqDhB,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EA7DM,OAAqC,GA8DhD;;AAlET,AAWQ,cAXM,AAAA,KAAK,CASf,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,EDgEE,OAAO,GC/Dd;;AAbT,AAeQ,cAfM,AAAA,KAAK,CASf,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,ED4DW,OAAO,GC3DvB;;AAjBT,AAuBQ,cAvBM,AAAA,KAAK,AAqBd,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDoDS,OAAO;ECnDtB,YAAY,EAAE,IAAI;EAClB,IAAI,EDkDW,OAAO,GCjDvB;;AA3BT,AAgCQ,cAhCM,AAAA,KAAK,AA8Bd,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,ED2CS,OAAO;EC1CtB,YAAY,EAAE,CAAC;EACf,IAAI,EDyCW,OAAO,GCxCvB;;AApCT,AA0CQ,cA1CM,AAAA,KAAK,AAwCd,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,EDiCE,OAAO;EChCb,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,KAAK,AAwCd,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,ED6BW,OAAO;EC5BtB,MAAM,ED4BS,OAAO;EC3BtB,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,KAAK,CAqDf,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,KAAK,CAqDf,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EApDS,OAAqC;IAqDlD,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,KAAK,CAqDf,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EAzDK,OAAsC,GA0DhD;IA/DT,AAgEQ,cAhEM,AAAA,KAAK,CAqDf,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EA7DM,OAAqC,GA8DhD;;AAlET,AAWQ,cAXM,AAAA,WAAW,CASrB,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,EDoEE,OAAO,GCnEd;;AAbT,AAeQ,cAfM,AAAA,WAAW,CASrB,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,EDgEW,OAAO,GC/DvB;;AAjBT,AAuBQ,cAvBM,AAAA,WAAW,AAqBpB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,ED2DA,OAAO;EC1Db,YAAY,EAAE,IAAI;EAClB,IAAI,EDsDW,OAAO,GCrDvB;;AA3BT,AAgCQ,cAhCM,AAAA,WAAW,AA8BpB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDkDA,OAAO;ECjDb,YAAY,EAAE,CAAC;EACf,IAAI,ED6CW,OAAO,GC5CvB;;AApCT,AA0CQ,cA1CM,AAAA,WAAW,AAwCpB,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,EDwCW,OAAO;ECvCtB,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,WAAW,AAwCpB,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,EDiCW,OAAO;EChCtB,MAAM,EDmCA,OAAO;EClCb,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,WAAW,CAqDrB,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,WAAW,CAqDrB,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EDwBI,OAAO;ICvBf,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,WAAW,CAqDrB,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EDmBW,OAAO,GClBvB;IA/DT,AAgEQ,cAhEM,AAAA,WAAW,CAqDrB,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EDgBE,OAAO,GCfd;;AAlET,AAWQ,cAXM,AAAA,cAAc,CASxB,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,ED2EE,OAAO,GC1Ed;;AAbT,AAeQ,cAfM,AAAA,cAAc,CASxB,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,EDuEW,OAAO,GCtEvB;;AAjBT,AAuBQ,cAvBM,AAAA,cAAc,AAqBvB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDkEA,OAAO;ECjEb,YAAY,EAAE,IAAI;EAClB,IAAI,ED6DW,OAAO,GC5DvB;;AA3BT,AAgCQ,cAhCM,AAAA,cAAc,AA8BvB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDyDA,OAAO;ECxDb,YAAY,EAAE,CAAC;EACf,IAAI,EDoDW,OAAO,GCnDvB;;AApCT,AA0CQ,cA1CM,AAAA,cAAc,AAwCvB,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,ED+CW,OAAO;EC9CtB,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,cAAc,AAwCvB,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,EDwCW,OAAO;ECvCtB,MAAM,ED0CA,OAAO;ECzCb,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,cAAc,CAqDxB,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,cAAc,CAqDxB,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,ED+BI,OAAO;IC9Bf,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,cAAc,CAqDxB,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,ED0BW,OAAO,GCzBvB;IA/DT,AAgEQ,cAhEM,AAAA,cAAc,CAqDxB,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EDuBE,OAAO,GCtBd;;AAlET,AAWQ,cAXM,AAAA,gBAAgB,CAS1B,MAAM,CACJ,OAAO,AACJ,MAAM,CAAC;EACN,IAAI,EDkFE,OAAO,GCjFd;;AAbT,AAeQ,cAfM,AAAA,gBAAgB,CAS1B,MAAM,CACJ,OAAO,AAKJ,MAAM,CAAC;EACN,IAAI,ED8EW,OAAO,GC7EvB;;AAjBT,AAuBQ,cAvBM,AAAA,gBAAgB,AAqBzB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,EDsES,OAAO;ECrEtB,YAAY,EAAE,IAAI;EAClB,IAAI,EDoEW,OAAO,GCnEvB;;AA3BT,AAgCQ,cAhCM,AAAA,gBAAgB,AA8BzB,iBAAiB,CAChB,MAAM,CACJ,OAAO,CAAC;EACN,MAAM,ED6DS,OAAO;EC5DtB,YAAY,EAAE,CAAC;EACf,IAAI,ED2DW,OAAO,GC1DvB;;AApCT,AA0CQ,cA1CM,AAAA,gBAAgB,AAwCzB,kBAAkB,CACjB,MAAM,CACJ,OAAO,CAAC;EACN,IAAI,EDmDE,OAAO;EClDb,MAAM,EAAE,IAAI,GACb;;AA7CT,AA8CQ,cA9CM,AAAA,gBAAgB,AAwCzB,kBAAkB,CACjB,MAAM,CAKJ,aAAa,CAAC;EACZ,IAAI,ED+CW,OAAO;EC9CtB,MAAM,ED8CS,OAAO;EC7CtB,YAAY,EAAE,IAAI,GACnB;;AAlDT,AAqDI,cArDU,AAAA,gBAAgB,CAqD1B,YAAY,CAAC;EACX,cAAc,EAAE,IAAI;EACpB,WAAW,EAAE,IAAI,GAalB;EApEL,AAyDM,cAzDQ,AAAA,gBAAgB,CAqD1B,YAAY,CAIV,WAAW,CAAC;IACV,IAAI,EApDS,OAAqC;IAqDlD,SAAS,EAAE,GAAG;IACd,MAAM,EAAE,OAAO,GAOhB;IAnEP,AA6DQ,cA7DM,AAAA,gBAAgB,CAqD1B,YAAY,CAIV,WAAW,AAIR,MAAM,CAAC;MACN,IAAI,EAzDK,KAAsC,GA0DhD;IA/DT,AAgEQ,cAhEM,AAAA,gBAAgB,CAqD1B,YAAY,CAIV,WAAW,AAOR,MAAM,CAAC;MACN,IAAI,EA7DM,OAAqC,GA8DhD" 10 | } -------------------------------------------------------------------------------- /assets/chessboard.scss: -------------------------------------------------------------------------------- 1 | @import "chessboard-theme"; 2 | 3 | // define markers (for all themes) 4 | .cm-chessboard { 5 | 6 | .board { 7 | &.input-enabled { 8 | .square { 9 | cursor: pointer; 10 | } 11 | } 12 | } 13 | 14 | .coordinates, .markers-layer, .pieces-layer, .markers-top-layer { 15 | pointer-events: none; 16 | } 17 | } 18 | 19 | .cm-chessboard-content { 20 | .list-inline { 21 | padding-left: 0; 22 | list-style: none; 23 | } 24 | 25 | .list-inline-item { 26 | display: inline-block; 27 | 28 | &:not(:last-child) { 29 | margin-right: 1rem; 30 | } 31 | } 32 | } 33 | 34 | .cm-chessboard-content { 35 | 36 | .list-inline { 37 | padding-left: 0; 38 | list-style: none; 39 | } 40 | 41 | .list-inline-item { 42 | display: inline-block; 43 | 44 | &:not(:last-child) { 45 | margin-right: 1rem; 46 | } 47 | } 48 | } 49 | 50 | .cm-chessboard-accessibility.visually-hidden { 51 | width: 1px; 52 | height: 1px; 53 | padding: 0; 54 | margin: -1px; 55 | overflow: hidden; 56 | clip: rect(0, 0, 0, 0); 57 | white-space: nowrap; 58 | border: 0; 59 | } 60 | 61 | // define themes 62 | @include cm-chessboard-theme( 63 | "default", // name 64 | #ecdab9, #c5a076 // squares 65 | ); 66 | @include cm-chessboard-theme( 67 | "default-contrast", // name 68 | #ecdab9, #c5a076, // squares 69 | #333, #333, // coordinates in squares (white, black) 70 | ); 71 | @include cm-chessboard-theme( 72 | "green", // name 73 | #E0DDCC, #4c946a // squares 74 | ); 75 | @include cm-chessboard-theme( 76 | "blue", // name 77 | #d8ecfb, #86afcf // squares 78 | ); 79 | @include cm-chessboard-theme( 80 | "chess-club", // name 81 | #E6D3B1, #AF6B3F, // squares 82 | #AF6B3F, #E6D3B1, // coordinates in squares (white, black) 83 | #E6D3B1, // coordinates in frame-mode 84 | #692e2b, #692e2b // border and frame background 85 | ); 86 | @include cm-chessboard-theme( 87 | "chessboard-js", // name 88 | #f0d9b5, #b58863, // squares 89 | #b58863, #f0d9b5, // coordinates in squares (white, black) 90 | #404040, // coordinates in frame-mode 91 | #404040, #f0d9b5 // border and frame background 92 | ); 93 | @include cm-chessboard-theme( 94 | "black-and-white", // name 95 | #ffffff, #9c9c9c // squares 96 | ); 97 | -------------------------------------------------------------------------------- /assets/extensions/arrows/arrows.css: -------------------------------------------------------------------------------- 1 | .cm-chessboard .arrow-warning .arrow-head { 2 | fill: orange; 3 | fill-rule: nonzero; 4 | } 5 | .cm-chessboard .arrow-warning .arrow-line { 6 | stroke: orange; 7 | stroke-linecap: round; 8 | opacity: 0.6; 9 | } 10 | .cm-chessboard .arrow-danger .arrow-head { 11 | fill: red; 12 | fill-rule: nonzero; 13 | } 14 | .cm-chessboard .arrow-danger .arrow-line { 15 | stroke: red; 16 | stroke-linecap: round; 17 | opacity: 0.5; 18 | } 19 | .cm-chessboard .arrow-success .arrow-head { 20 | fill: green; 21 | fill-rule: nonzero; 22 | } 23 | .cm-chessboard .arrow-success .arrow-line { 24 | stroke: green; 25 | stroke-linecap: round; 26 | opacity: 0.5; 27 | } 28 | .cm-chessboard .arrow-info .arrow-head { 29 | fill: blue; 30 | fill-rule: nonzero; 31 | } 32 | .cm-chessboard .arrow-info .arrow-line { 33 | stroke: blue; 34 | stroke-linecap: butt; 35 | opacity: 0.4; 36 | } 37 | .cm-chessboard .arrow-neutral .arrow-head { 38 | fill: black; 39 | fill-rule: nonzero; 40 | } 41 | .cm-chessboard .arrow-neutral .arrow-line { 42 | stroke: black; 43 | stroke-linecap: round; 44 | opacity: 0.5; 45 | } 46 | 47 | /*# sourceMappingURL=arrows.css.map */ 48 | -------------------------------------------------------------------------------- /assets/extensions/arrows/arrows.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["arrows.scss"],"names":[],"mappings":"AAEI;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAIF;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAIF;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAIF;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAIF;EACE;EACA;;AAEF;EACE;EACA;EACA","file":"arrows.css"} -------------------------------------------------------------------------------- /assets/extensions/arrows/arrows.scss: -------------------------------------------------------------------------------- 1 | .cm-chessboard { 2 | .arrow-warning { 3 | .arrow-head { 4 | fill: orange; 5 | fill-rule: nonzero; 6 | } 7 | .arrow-line { 8 | stroke: orange; 9 | stroke-linecap: round; 10 | opacity: 0.6; 11 | } 12 | } 13 | .arrow-danger { 14 | .arrow-head { 15 | fill: red; 16 | fill-rule: nonzero; 17 | } 18 | .arrow-line { 19 | stroke: red; 20 | stroke-linecap: round; 21 | opacity: 0.5; 22 | } 23 | } 24 | .arrow-success { 25 | .arrow-head { 26 | fill: green; 27 | fill-rule: nonzero; 28 | } 29 | .arrow-line { 30 | stroke: green; 31 | stroke-linecap: round; 32 | opacity: 0.5; 33 | } 34 | } 35 | .arrow-info { 36 | .arrow-head { 37 | fill: blue; 38 | fill-rule: nonzero; 39 | } 40 | .arrow-line { 41 | stroke: blue; 42 | stroke-linecap: butt; 43 | opacity: 0.4; 44 | } 45 | } 46 | .arrow-neutral { 47 | .arrow-head { 48 | fill: black; 49 | fill-rule: nonzero; 50 | } 51 | .arrow-line { 52 | stroke: black; 53 | stroke-linecap: round; 54 | opacity: 0.5; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /assets/extensions/arrows/arrows.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaack/cm-chessboard/3c73769cc0af08b45bacc3d43922b841f277a493/assets/extensions/arrows/arrows.sketch -------------------------------------------------------------------------------- /assets/extensions/arrows/arrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | arrows 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/extensions/markers/markers.css: -------------------------------------------------------------------------------- 1 | .cm-chessboard .markers { 2 | pointer-events: none; 3 | } 4 | .cm-chessboard .markers .marker.marker-frame { 5 | stroke: black; 6 | stroke-width: 1.8px; 7 | opacity: 0.5; 8 | } 9 | .cm-chessboard .markers .marker.marker-frame-primary, .cm-chessboard .markers .marker.marker-frame-info { 10 | stroke: blue; 11 | stroke-width: 1.8px; 12 | opacity: 0.4; 13 | } 14 | .cm-chessboard .markers .marker.marker-frame-danger { 15 | stroke: red; 16 | stroke-width: 1.8px; 17 | opacity: 0.4; 18 | } 19 | .cm-chessboard .markers .marker.marker-frame-warning { 20 | stroke: orange; 21 | stroke-width: 1.8px; 22 | opacity: 0.4; 23 | } 24 | .cm-chessboard .markers .marker.marker-circle { 25 | stroke: black; 26 | stroke-width: 3px; 27 | opacity: 0.3; 28 | } 29 | .cm-chessboard .markers .marker.marker-circle-primary { 30 | stroke: blue; 31 | stroke-width: 3px; 32 | opacity: 0.4; 33 | } 34 | .cm-chessboard .markers .marker.marker-circle-info { 35 | stroke: blue; 36 | stroke-width: 3px; 37 | opacity: 0.4; 38 | } 39 | .cm-chessboard .markers .marker.marker-circle-info-filled { 40 | stroke: blue; 41 | fill: blue; 42 | stroke-width: 3px; 43 | opacity: 0.4; 44 | } 45 | .cm-chessboard .markers .marker.marker-circle-danger { 46 | stroke: red; 47 | stroke-width: 3px; 48 | opacity: 0.4; 49 | } 50 | .cm-chessboard .markers .marker.marker-circle-danger-filled { 51 | stroke: red; 52 | fill: red; 53 | stroke-width: 3px; 54 | opacity: 0.4; 55 | } 56 | .cm-chessboard .markers .marker.marker-circle-warning { 57 | stroke: orange; 58 | stroke-width: 3px; 59 | opacity: 0.4; 60 | } 61 | .cm-chessboard .markers .marker.marker-circle-warning-filled { 62 | stroke: orange; 63 | fill: orange; 64 | stroke-width: 3px; 65 | opacity: 0.4; 66 | } 67 | .cm-chessboard .markers .marker.marker-circle-success { 68 | stroke: green; 69 | stroke-width: 3px; 70 | opacity: 0.4; 71 | } 72 | .cm-chessboard .markers .marker.marker-circle-success-filled { 73 | stroke: green; 74 | fill: green; 75 | stroke-width: 3px; 76 | opacity: 0.4; 77 | } 78 | .cm-chessboard .markers .marker.marker-square { 79 | fill: black; 80 | opacity: 0.11; 81 | } 82 | .cm-chessboard .markers .marker.marker-dot { 83 | fill: black; 84 | opacity: 0.2; 85 | } 86 | .cm-chessboard .markers .marker.marker-bevel { 87 | fill: black; 88 | opacity: 0.2; 89 | } 90 | 91 | /*# sourceMappingURL=markers.css.map */ 92 | -------------------------------------------------------------------------------- /assets/extensions/markers/markers.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["markers.scss"],"names":[],"mappings":"AAOE;EACE;;AAGE;EACE,QAZO;EAaP;EACA;;AAGF;EACE,QAjBY;EAkBZ;EACA;;AAGF;EACE,QAtBc;EAuBd;EACA;;AAGF;EACE,QA3Be;EA4Bf;EACA;;AAGF;EACE,QApCO;EAqCP;EACA;;AAGF;EACE,QAzCY;EA0CZ;EACA;;AAGF;EACE,QA/CY;EAgDZ;EACA;;AAGF;EACE,QArDY;EAsDZ,MAtDY;EAuDZ;EACA;;AAGF;EACE,QA3Dc;EA4Dd;EACA;;AAGF;EACE,QAjEc;EAkEd,MAlEc;EAmEd;EACA;;AAGF;EACE,QAvEe;EAwEf;EACA;;AAGF;EACE,QA7Ee;EA8Ef,MA9Ee;EA+Ef;EACA;;AAGF;EACE,QAnFe;EAoFf;EACA;;AAGF;EACE,QAzFe;EA0Ff,MA1Fe;EA2Ff;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA","file":"markers.css"} -------------------------------------------------------------------------------- /assets/extensions/markers/markers.scss: -------------------------------------------------------------------------------- 1 | $marker-color: black; 2 | $marker-color-info: blue; 3 | $marker-color-danger: red; 4 | $marker-color-warning: orange; 5 | $marker-color-success: green; 6 | 7 | .cm-chessboard { 8 | .markers { 9 | pointer-events: none; 10 | 11 | .marker { 12 | &.marker-frame { 13 | stroke: $marker-color; 14 | stroke-width: 1.8px; 15 | opacity: 0.5; 16 | } 17 | 18 | &.marker-frame-primary, &.marker-frame-info { 19 | stroke: $marker-color-info; 20 | stroke-width: 1.8px; 21 | opacity: 0.4; 22 | } 23 | 24 | &.marker-frame-danger { 25 | stroke: $marker-color-danger; 26 | stroke-width: 1.8px; 27 | opacity: 0.4; 28 | } 29 | 30 | &.marker-frame-warning { 31 | stroke: $marker-color-warning; 32 | stroke-width: 1.8px; 33 | opacity: 0.4; 34 | } 35 | 36 | &.marker-circle { 37 | stroke: $marker-color; 38 | stroke-width: 3px; 39 | opacity: 0.3; 40 | } 41 | 42 | &.marker-circle-primary { 43 | stroke: $marker-color-info; 44 | stroke-width: 3px; 45 | opacity: 0.4; 46 | } 47 | 48 | &.marker-circle-info { 49 | stroke: $marker-color-info; 50 | stroke-width: 3px; 51 | opacity: 0.4; 52 | } 53 | 54 | &.marker-circle-info-filled { 55 | stroke: $marker-color-info; 56 | fill: $marker-color-info; 57 | stroke-width: 3px; 58 | opacity: 0.4; 59 | } 60 | 61 | &.marker-circle-danger { 62 | stroke: $marker-color-danger; 63 | stroke-width: 3px; 64 | opacity: 0.4; 65 | } 66 | 67 | &.marker-circle-danger-filled { 68 | stroke: $marker-color-danger; 69 | fill: $marker-color-danger; 70 | stroke-width: 3px; 71 | opacity: 0.4; 72 | } 73 | 74 | &.marker-circle-warning { 75 | stroke: $marker-color-warning; 76 | stroke-width: 3px; 77 | opacity: 0.4; 78 | } 79 | 80 | &.marker-circle-warning-filled { 81 | stroke: $marker-color-warning; 82 | fill: $marker-color-warning; 83 | stroke-width: 3px; 84 | opacity: 0.4; 85 | } 86 | 87 | &.marker-circle-success { 88 | stroke: $marker-color-success; 89 | stroke-width: 3px; 90 | opacity: 0.4; 91 | } 92 | 93 | &.marker-circle-success-filled { 94 | stroke: $marker-color-success; 95 | fill: $marker-color-success; 96 | stroke-width: 3px; 97 | opacity: 0.4; 98 | } 99 | 100 | &.marker-square { 101 | fill: black; 102 | opacity: 0.11; 103 | } 104 | 105 | &.marker-dot { 106 | fill: black; 107 | opacity: 0.2; 108 | } 109 | 110 | &.marker-bevel { 111 | fill: black; 112 | opacity: 0.2; 113 | } 114 | 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /assets/extensions/markers/markers.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | cm-chessboard markers 11 | Markers for cm-chessboard (https://shaack.com/projekte/cm-chessboard/) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /assets/extensions/promotion-dialog/promotion-dialog.css: -------------------------------------------------------------------------------- 1 | svg.cm-chessboard .promotion-dialog-group rect, svg.cm-chessboard .promotion-dialog-group g[data-piece] { 2 | animation: fade-in 0.25s ease-in; } 3 | 4 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog { 5 | fill: white; 6 | fill-opacity: 0.7; 7 | stroke: rgba(0, 0, 0, 0.4); } 8 | 9 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog-button { 10 | fill: transparent; 11 | cursor: pointer; } 12 | svg.cm-chessboard .promotion-dialog-group .promotion-dialog-button:hover { 13 | fill: rgba(0, 0, 0, 0.2); } 14 | 15 | svg.cm-chessboard .promotion-dialog-group .piece { 16 | pointer-events: none; } 17 | 18 | @keyframes fade-in { 19 | 0% { 20 | opacity: 0; } 21 | 100% { 22 | opacity: 1; } } 23 | 24 | /*# sourceMappingURL=promotion-dialog.css.map */ -------------------------------------------------------------------------------- /assets/extensions/promotion-dialog/promotion-dialog.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "promotion-dialog.css", 4 | "sources": [ 5 | "promotion-dialog.scss" 6 | ], 7 | "names": [], 8 | "mappings": "AAAA,AAGI,GAHD,AAAA,cAAc,CACf,uBAAuB,CAErB,IAAI,EAHR,GAAG,AAAA,cAAc,CACf,uBAAuB,CAEf,CAAC,CAAA,AAAA,UAAC,AAAA,EAAY;EAClB,SAAS,EAAE,qBAAqB,GACjC;;AALL,AAOI,GAPD,AAAA,cAAc,CACf,uBAAuB,CAMrB,iBAAiB,CAAC;EAChB,IAAI,EAAE,KAAK;EACX,YAAY,EAAE,GAAG;EACjB,MAAM,EAAE,kBAAkB,GAC3B;;AAXL,AAaI,GAbD,AAAA,cAAc,CACf,uBAAuB,CAYrB,wBAAwB,CAAC;EACvB,IAAI,EAAE,WAAW;EACjB,MAAM,EAAE,OAAO,GAKhB;EApBL,AAiBM,GAjBH,AAAA,cAAc,CACf,uBAAuB,CAYrB,wBAAwB,CAIpB,KAAK,CAAC;IACN,IAAI,EAAE,kBAAkB,GACzB;;AAnBP,AAsBI,GAtBD,AAAA,cAAc,CACf,uBAAuB,CAqBrB,MAAM,CAAC;EACL,cAAc,EAAE,IAAI,GACrB;;AAIL,UAAU,CAAV,OAAU;EACR,EAAE;IACA,OAAO,EAAE,CAAC;EAEZ,IAAI;IACF,OAAO,EAAE,CAAC" 9 | } -------------------------------------------------------------------------------- /assets/extensions/promotion-dialog/promotion-dialog.scss: -------------------------------------------------------------------------------- 1 | svg.cm-chessboard { 2 | .promotion-dialog-group { 3 | 4 | rect, g[data-piece] { 5 | animation: fade-in 0.25s ease-in; 6 | } 7 | 8 | .promotion-dialog { 9 | fill: white; 10 | fill-opacity: 0.7; 11 | stroke: rgba(0, 0, 0, 0.4); 12 | } 13 | 14 | .promotion-dialog-button { 15 | fill: transparent; 16 | cursor: pointer; 17 | 18 | &:hover { 19 | fill: rgba(0, 0, 0, 0.2) 20 | } 21 | } 22 | 23 | .piece { 24 | pointer-events: none; 25 | } 26 | } 27 | } 28 | 29 | @keyframes fade-in { 30 | 0% { 31 | opacity: 0; 32 | } 33 | 100% { 34 | opacity: 1; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/destroy-many-boards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 26 | 27 | 28 |

cm-chessboard

29 |

Stress Test: Create and destroy 5,000 boards ⇨

30 |
31 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /examples/different-styles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 |

cm-chessboard

12 |

Example: Different styles and piece sets

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/enable-input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Input enabled without validation

14 |

Input enabled on both sides, without move validation. 15 |

16 |

Additionally, the Markers extension is used with its 17 | autoMarkers ability.

18 |
19 |
20 | const board = new Chessboard(document.getElementById("board"), {
21 |     position: FEN.start,
22 |     assetsUrl: "../assets/",
23 |     style: {pieces: {file: "pieces/staunty.svg"}},
24 |     extensions: [{class: Markers}]
25 | })
26 | 
27 | board.enableMoveInput(inputHandler)
28 | function inputHandler(event) {
29 |     console.log(event)
30 |     switch (event.type) {
31 |         case INPUT_EVENT_TYPE.moveInputStarted:
32 |             log(`moveInputStarted: ${event.squareFrom}`)
33 |             return true // false cancels move
34 |         case INPUT_EVENT_TYPE.validateMoveInput:
35 |             log(`validateMoveInput: ${event.squareFrom}-${event.squareTo}`)
36 |             return true // false cancels move
37 |         case INPUT_EVENT_TYPE.moveInputCanceled:
38 |             log(`moveInputCanceled`)
39 |             break
40 |         case INPUT_EVENT_TYPE.moveInputFinished:
41 |             log(`moveInputFinished`)
42 |             break
43 |         case INPUT_EVENT_TYPE.movingOverSquare:
44 |             log(`movingOverSquare: ${event.squareTo}`)
45 |             break
46 |     }
47 | }
48 | 
49 | 52 |
53 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/extensions/accessibility-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Accessibility extension

14 |

15 | This example shows, how the accessibility extension from cm-chessboard works. 16 | It allows a better usage of the board for visually impaired 17 | people. 18 |

19 |

20 | The validate moves example has also 21 | accessibility features enabled, but visually hidden. 22 |

23 |

Features of the Accessibility extension

24 | 31 |

The chessboard

32 |
33 | 36 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/extensions/arrows-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Arrows extension

14 |
15 |
16 |

Example code

17 |
18 | const chessboard = new Chessboard(document.getElementById("board"), {
19 |     position: "rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11",
20 |     assetsUrl: "../../assets/",
21 |     extensions: [{class: Arrows}]
22 | })
23 | chessboard.addArrow(ARROW_TYPE.default, "f3", "d5")
24 | chessboard.addArrow(ARROW_TYPE.default, "b8", "c6")
25 | chessboard.addArrow(ARROW_TYPE.pointy, "d2", "d3")
26 | chessboard.addArrow(ARROW_TYPE.danger, "g5", "e6")
27 | console.log(chessboard.getArrows())
28 | 
29 |

Methods

30 |

addArrow(type, from, to)

31 |

removeArrows(type, from, to)

32 |

33 | To remove all arrows, call chessboard.removeArrows() without parameters. To remove all arrows of a 34 | specific 35 | type (type "danger"), call chessboard.removeArrows(ARROW_TYPE.danger). To remove all arrows starting at 36 | "e2" 37 | you can call chessboard.removeArrows(undefined, "e2") and so on... 38 |

39 |

getArrows(type, from, to)

40 |

41 | To get all arrows, call chessboard.getArrows() without parameters, as with removeArrows(type, 42 | from, to). 43 |

44 | 45 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /examples/extensions/html-layer-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 28 | 29 | 30 |

cm-chessboard

31 |

Example: HtmlLayer Extension

32 |

Use this extension to add a layer over the board which contains HTML.

33 |
34 |
35 | 36 | 37 |
38 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/extensions/markers-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Markers Extension with pointer input

14 |

Use left and right click to create markers on the boards. The second board shows the usage of autoMarkers, when moving a piece.

15 |
16 |
17 |
18 | 19 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/extensions/persistence-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Persistence Extension

14 |

This extension stores the board position in localStorage and automatically loads the board on creation.

15 |

Alpha version, do not use in production.

16 |
17 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/extensions/promotion-dialog-extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | cm-chessboard promotion extension 13 | 14 | 15 |

cm-chessboard

16 |

Example of the cm-chessboard PromotionDialog extension

17 |
18 |
19 | 22 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/extensions/right-click-annotator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

cm-chessboard

14 |

Example: RightClickAnnotator extension

15 | 20 |
21 | 22 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/many-boards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 |

cm-chessboard

21 |

Example: 100 boards in one page

22 |
23 | 24 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/pieces-animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 |

cm-chessboard

12 |

Example: Set different positions, with animation

13 |

Animations are queued automatically.

14 |
15 |
16 |

Not animated

17 | 18 | 19 | 20 | 21 | 22 | 23 |

Animated

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
<button onclick="window.board.setPosition('rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR')">Position 3</button>
32 |
33 | Move piece 36 | 37 |
board.movePiece(squareFrom, squareTo)
38 |
39 | 40 |
41 | board.setPiece("e4", PIECE.wn)
42 | await board.movePiece("e4", "c3")
43 | await board.movePiece("c3", "d5")
44 | await board.movePiece("d5", "f4")
45 | 
46 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/pointer-events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |

cm-chessboard

13 |

Example: Pointer Events

14 |

Click on the board, the events are logged into the console.

15 |
16 | 32 | 33 | -------------------------------------------------------------------------------- /examples/responsive-board.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 |

cm-chessboard

12 |

Example: Responsive chessboard with an aspect ratio of 0.9

13 |
14 |
15 | new Chessboard(document.getElementById("board"), {
16 |         position: FEN.start,
17 |         assetsUrl: "../assets/",
18 |         style: {
19 |             aspectRatio: 0.9,
20 |             pieces: {file: "pieces/staunty.svg"},
21 |             borderType: BORDER_TYPE.frame
22 |         },
23 |         extensions: [{class: AutoBorderNone}]
24 |     })
25 | 
26 |

This example also uses the "AutoBorderNone" extension, which switches off the frame border on smaller boards.

27 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/sandbox/arrows-extension-multiple.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Arrows Example 8 | 9 | 10 | 11 | 15 | 16 | 23 | 24 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/simple-boards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 |

cm-chessboard

12 |

Example: Simple chessboards, view only

13 |
14 |
15 |
16 |
17 | new Chessboard(document.getElementById("board1"), {
18 |     assetsUrl: "../assets/",
19 |     position: FEN.start
20 | })
21 | new Chessboard(document.getElementById("board2"), {
22 |     assetsUrl: "../assets/",
23 |     position: "rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11",
24 |     style: {pieces: {file: "pieces/staunty.svg"},cssClass: "green", borderType: BORDER_TYPE.frame},
25 |     orientation: COLOR.black
26 | })
27 | 
28 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/styles/examples.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #fff; 3 | background-color: #222; 4 | padding: 20px; 5 | margin: 0; 6 | font-family: "Helvetica Neue", sans-serif; 7 | line-height: 1.3; 8 | font-size: 18px; } 9 | 10 | a { 11 | color: #516CD4; } 12 | a:visited { 13 | color: #929292; } 14 | a:hover { 15 | color: #E2FEDE; } 16 | 17 | h1, h1 a { 18 | margin-top: 0; 19 | font-size: 39.6px; 20 | color: #CD7338; } 21 | 22 | h1 { 23 | margin-bottom: 10px; } 24 | 25 | h2 { 26 | margin-bottom: 15px; 27 | font-size: 25.2px; 28 | color: #CD7338; } 29 | 30 | pre { 31 | font-family: "courier new", monospace; 32 | font-size: 90%; 33 | background-color: #111; 34 | color: #eee; 35 | padding: 5px; 36 | overflow: auto; } 37 | 38 | div.board { 39 | float: left; 40 | max-width: 500px; 41 | width: calc(100vw - 40px); 42 | margin-right: 20px; 43 | margin-bottom: 20px; } 44 | div.board.board-small { 45 | max-width: 320px; } 46 | div.board.board-large { 47 | max-width: 680px; } 48 | 49 | .clearfix::after { 50 | content: ""; 51 | clear: both; 52 | display: table; } 53 | 54 | button { 55 | padding: 5px 20px; 56 | font-size: 92%; 57 | margin-right: 10px; 58 | margin-bottom: 10px; 59 | background-color: #999; 60 | color: black; 61 | border: none; 62 | border-radius: 3px; 63 | cursor: pointer; } 64 | button:hover { 65 | background-color: #777; } 66 | 67 | input[type=text] { 68 | font-size: 92%; 69 | padding: 5px; 70 | border-radius: 3px; 71 | border: none; } 72 | 73 | li { 74 | padding: 0.05rem; } 75 | 76 | .board-max-width { 77 | max-width: 640px; } 78 | 79 | /*# sourceMappingURL=examples.css.map */ -------------------------------------------------------------------------------- /examples/styles/examples.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "examples.css", 4 | "sources": [ 5 | "examples.scss" 6 | ], 7 | "names": [], 8 | "mappings": "AASA,AAAA,IAAI,CAAC;EACH,KAAK,EAPG,IAAI;EAQZ,gBAAgB,EATL,IAAI;EAUf,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,CAAC;EACT,WAAW,EAAE,4BAA4B;EACzC,WAAW,EAAE,GAAG;EAChB,SAAS,EATC,IAAI,GAUf;;AAED,AAAA,CAAC,CAAC;EACA,KAAK,EApBA,OAAO,GA6Bb;EAVD,AAGE,CAHD,CAGG,OAAO,CAAC;IACR,KAAK,EAAE,OAAO,GACf;EALH,AAOE,CAPD,CAOG,KAAK,CAAC;IACN,KAAK,EA1BI,OAAO,GA2BjB;;AAGH,AAAA,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;EACP,UAAU,EAAE,CAAC;EACb,SAAS,EAAE,MAAgB;EAC3B,KAAK,EA9BE,OAAO,GA+Bf;;AAED,AAAA,EAAE,CAAC;EACD,aAAa,EAAE,IAAI,GACpB;;AAED,AAAA,EAAE,CAAC;EACD,aAAa,EAAE,IAAI;EACnB,SAAS,EAAE,MAAgB;EAC3B,KAAK,EAxCE,OAAO,GAyCf;;AAED,AAAA,GAAG,CAAC;EACF,WAAW,EAAE,wBAAwB;EACrC,SAAS,EAAE,GAAG;EACd,gBAAgB,EA7CA,IAAI;EA8CpB,KAAK,EA7CA,IAAI;EA8CT,OAAO,EAAE,GAAG;EACZ,QAAQ,EAAE,IAAI,GACf;;AAED,AAAA,GAAG,AAAA,MAAM,CAAC;EACR,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,KAAK;EAChB,KAAK,EAAE,kBAAkB;EACzB,YAAY,EAAE,IAAI;EAClB,aAAa,EAAE,IAAI,GAOpB;EAZD,AAME,GANC,AAAA,MAAM,AAMN,YAAY,CAAC;IACZ,SAAS,EAAE,KAAK,GACjB;EARH,AASE,GATC,AAAA,MAAM,AASN,YAAY,CAAC;IACZ,SAAS,EAAE,KAAK,GACjB;;AAGH,AAAA,SAAS,EAAE,KAAK,CAAC;EACf,OAAO,EAAE,EAAE;EACX,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,KAAK,GACf;;AAED,AAAA,MAAM,CAAC;EACL,OAAO,EAAE,QAAQ;EACjB,SAAS,EAAE,GAAG;EACd,YAAY,EAAE,IAAI;EAClB,aAAa,EAAE,IAAI;EACnB,gBAAgB,EAAE,IAAI;EACtB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,OAAO,GAKhB;EAdD,AAWE,MAXI,CAWF,KAAK,CAAC;IACN,gBAAgB,EAAE,IAAI,GACvB;;AAGH,AAAA,KAAK,CAAA,AAAA,IAAC,CAAD,IAAC,AAAA,EAAW;EACf,SAAS,EAAE,GAAG;EACd,OAAO,EAAE,GAAG;EACZ,aAAa,EAAE,GAAG;EAClB,MAAM,EAAE,IAAI,GACb;;AAED,AAAA,EAAE,CAAC;EACD,OAAO,EAAE,OAAO,GAEjB;;AAED,AAAA,gBAAgB,CAAC;EACf,SAAS,EAAE,KAAK,GACjB" 9 | } -------------------------------------------------------------------------------- /examples/styles/examples.scss: -------------------------------------------------------------------------------- 1 | $link: #516CD4; 2 | $link-hover: #E2FEDE; 3 | $background: #222; 4 | $default: #fff; 5 | $color2: #CD7338; 6 | $code-background: #111; 7 | $code: #eee; 8 | $font-size: 18px; 9 | 10 | body { 11 | color: $default; 12 | background-color: $background; 13 | padding: 20px; 14 | margin: 0; 15 | font-family: "Helvetica Neue", sans-serif; 16 | line-height: 1.3; 17 | font-size: $font-size; 18 | } 19 | 20 | a { 21 | color: $link; 22 | 23 | &:visited { 24 | color: #929292; 25 | } 26 | 27 | &:hover { 28 | color: $link-hover; 29 | } 30 | } 31 | 32 | h1, h1 a { 33 | margin-top: 0; 34 | font-size: $font-size * 2.2; 35 | color: $color2; 36 | } 37 | 38 | h1 { 39 | margin-bottom: 10px; 40 | } 41 | 42 | h2 { 43 | margin-bottom: 15px; 44 | font-size: $font-size * 1.4; 45 | color: $color2; 46 | } 47 | 48 | pre { 49 | font-family: "courier new", monospace; 50 | font-size: 90%; 51 | background-color: $code-background; 52 | color: $code; 53 | padding: 5px; 54 | overflow: auto; 55 | } 56 | 57 | div.board { 58 | float: left; 59 | max-width: 500px; 60 | width: calc(100vw - 40px); 61 | margin-right: 20px; 62 | margin-bottom: 20px; 63 | &.board-small { 64 | max-width: 320px; 65 | } 66 | &.board-large { 67 | max-width: 680px; 68 | } 69 | } 70 | 71 | .clearfix::after { 72 | content: ""; 73 | clear: both; 74 | display: table; 75 | } 76 | 77 | button { 78 | padding: 5px 20px; 79 | font-size: 92%; 80 | margin-right: 10px; 81 | margin-bottom: 10px; 82 | background-color: #999; 83 | color: black; 84 | border: none; 85 | border-radius: 3px; 86 | cursor: pointer; 87 | 88 | &:hover { 89 | background-color: #777; 90 | } 91 | } 92 | 93 | input[type=text] { 94 | font-size: 92%; 95 | padding: 5px; 96 | border-radius: 3px; 97 | border: none; 98 | } 99 | 100 | li { 101 | padding: 0.05rem; 102 | 103 | } 104 | 105 | .board-max-width { 106 | max-width: 640px; 107 | } 108 | -------------------------------------------------------------------------------- /examples/validate-moves.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

cm-chessboard

15 |

Example: Input enabled with move validation and promotion dialog

16 |

Input enabled for white. chess.js does the validation and answers with random moves.

17 |
18 |
19 | 20 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaack/cm-chessboard/3c73769cc0af08b45bacc3d43922b841f277a493/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cm-chessboard 6 | 7 | 8 | 9 | 10 | 11 |

cm-chessboard

12 |

A JavaScript chessboard which is

13 | 19 | 20 |

cm-chessboard is the chessboard of chessmail.de and is used every day by thousands of players.

21 | 22 |

cm-chessboard is easy to use: Repository and documentation on GitHub

23 |
24 |

Examples

25 | 35 |

Examples for the shipped cm-chessboard extensions

36 | 49 |
50 |

Chessboard

51 |
52 | 78 |
79 |

Tests

80 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cm-chessboard", 3 | "version": "8.10.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cm-chessboard", 9 | "version": "8.10.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "teevi": "^2.2.4" 13 | } 14 | }, 15 | "node_modules/teevi": { 16 | "version": "2.2.4", 17 | "resolved": "https://registry.npmjs.org/teevi/-/teevi-2.2.4.tgz", 18 | "integrity": "sha512-dlWBYsYlb2D/CppMenVKXpD/vCiVwlgFwfjofWqunziq8O1OeYhnXK3dnYOLRmNLhimsk/a3h+w5e03BnIExKQ==", 19 | "dev": true 20 | } 21 | }, 22 | "dependencies": { 23 | "teevi": { 24 | "version": "2.2.4", 25 | "resolved": "https://registry.npmjs.org/teevi/-/teevi-2.2.4.tgz", 26 | "integrity": "sha512-dlWBYsYlb2D/CppMenVKXpD/vCiVwlgFwfjofWqunziq8O1OeYhnXK3dnYOLRmNLhimsk/a3h+w5e03BnIExKQ==", 27 | "dev": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cm-chessboard", 3 | "version": "8.10.0", 4 | "description": "A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.", 5 | "keywords": [ 6 | "chess", 7 | "chessboard", 8 | "SVG", 9 | "ES6", 10 | "ECMAScript 6", 11 | "chessmail", 12 | "module", 13 | "widget" 14 | ], 15 | "type": "module", 16 | "module": "./src/Chessboard.js", 17 | "browser": "./src/Chessboard.js", 18 | "scripts": { 19 | "test": "tput setaf 4;echo 'Run test/index.html in your browser for unit testing.'; echo ''; exit 0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/shaack/cm-chessboard.git" 24 | }, 25 | "author": "Stefan Haack (shaack.com)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/shaack/cm-chessboard/issues" 29 | }, 30 | "homepage": "https://github.com/shaack/cm-chessboard#readme", 31 | "devDependencies": { 32 | "teevi": "^2.2.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Chessboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {ChessboardState} from "./model/ChessboardState.js" 8 | import {FEN, Position} from "./model/Position.js" 9 | import {PositionAnimationsQueue} from "./view/PositionAnimationsQueue.js" 10 | import {EXTENSION_POINT} from "./model/Extension.js" 11 | import {ChessboardView, COLOR, INPUT_EVENT_TYPE, BORDER_TYPE, POINTER_EVENTS} from "./view/ChessboardView.js" 12 | import {Utils} from "./lib/Utils.js" 13 | 14 | export const PIECE = { 15 | wp: "wp", wb: "wb", wn: "wn", wr: "wr", wq: "wq", wk: "wk", 16 | bp: "bp", bb: "bb", bn: "bn", br: "br", bq: "bq", bk: "bk" 17 | } 18 | export const PIECE_TYPE = { 19 | pawn: "p", knight: "n", bishop: "b", rook: "r", queen: "q", king: "k" 20 | } 21 | export const PIECES_FILE_TYPE = { 22 | svgSprite: "svgSprite" 23 | } 24 | export {COLOR} 25 | export {INPUT_EVENT_TYPE} 26 | export {POINTER_EVENTS} 27 | export {BORDER_TYPE} 28 | export {FEN} 29 | 30 | export class Chessboard { 31 | 32 | constructor(context, props = {}) { 33 | if (!context) { 34 | throw new Error("container element is " + context) 35 | } 36 | this.context = context 37 | this.id = (Math.random() + 1).toString(36).substring(2, 8) 38 | this.extensions = [] 39 | this.props = { 40 | position: FEN.empty, // set position as fen, use FEN.start or FEN.empty as shortcuts 41 | orientation: COLOR.white, // white on bottom 42 | responsive: true, // resize the board automatically to the size of the context element 43 | assetsUrl: "./assets/", // put all css and sprites in this folder, will be ignored for absolute urls of assets files 44 | assetsCache: true, // cache the sprites, deactivate if you want to use multiple pieces sets in one page 45 | style: { 46 | cssClass: "default", // set the css theme of the board, try "green", "blue" or "chess-club" 47 | showCoordinates: true, // show ranks and files 48 | borderType: BORDER_TYPE.none, // "thin" thin border, "frame" wide border with coordinates in it, "none" no border 49 | aspectRatio: 1, // height/width of the board 50 | pieces: { 51 | type: PIECES_FILE_TYPE.svgSprite, // pieces are in an SVG sprite, no other type supported for now 52 | file: "pieces/standard.svg", // the filename of the sprite in `assets/pieces/` or an absolute url like `https://…` or `/…` 53 | tileSize: 40 // the tile size in the sprite 54 | }, 55 | animationDuration: 300 // pieces animation duration in milliseconds. Disable all animations with `0` 56 | }, 57 | extensions: [ /* {class: ExtensionClass, props: { ... }} */] // add extensions here 58 | } 59 | Utils.mergeObjects(this.props, props) 60 | this.state = new ChessboardState() 61 | this.view = new ChessboardView(this) 62 | this.positionAnimationsQueue = new PositionAnimationsQueue(this) 63 | this.state.orientation = this.props.orientation 64 | // instantiate extensions 65 | for (const extensionData of this.props.extensions) { 66 | this.addExtension(extensionData.class, extensionData.props) 67 | } 68 | this.view.redrawBoard() 69 | this.state.position = new Position(this.props.position) 70 | this.view.redrawPieces() 71 | this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged) 72 | this.initialized = Promise.resolve() // deprecated 2023-09-19 don't use this anymore 73 | } 74 | 75 | // API // 76 | 77 | async setPiece(square, piece, animated = false) { 78 | const positionFrom = this.state.position.clone() 79 | this.state.position.setPiece(square, piece) 80 | this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged) 81 | return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated) 82 | } 83 | 84 | async movePiece(squareFrom, squareTo, animated = false) { 85 | const positionFrom = this.state.position.clone() 86 | this.state.position.movePiece(squareFrom, squareTo) 87 | this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged) 88 | return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated) 89 | } 90 | 91 | async setPosition(fen, animated = false) { 92 | const positionFrom = this.state.position.clone() 93 | const positionTo = new Position(fen) 94 | if (positionFrom.getFen() !== positionTo.getFen()) { 95 | this.state.position.setFen(fen) 96 | this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged) 97 | } 98 | return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated) 99 | } 100 | 101 | async setOrientation(color, animated = false) { 102 | const position = this.state.position.clone() 103 | if (this.boardTurning) { 104 | console.warn("setOrientation is only once in queue allowed") 105 | return 106 | } 107 | this.boardTurning = true 108 | return this.positionAnimationsQueue.enqueueTurnBoard(position, color, animated).then(() => { 109 | this.boardTurning = false 110 | this.state.invokeExtensionPoints(EXTENSION_POINT.boardChanged) 111 | }) 112 | } 113 | 114 | getPiece(square) { 115 | return this.state.position.getPiece(square) 116 | } 117 | 118 | getPosition() { 119 | return this.state.position.getFen() 120 | } 121 | 122 | getOrientation() { 123 | return this.state.orientation 124 | } 125 | 126 | enableMoveInput(eventHandler, color = undefined) { 127 | this.view.enableMoveInput(eventHandler, color) 128 | } 129 | 130 | disableMoveInput() { 131 | this.view.disableMoveInput() 132 | } 133 | 134 | isMoveInputEnabled() { 135 | return this.state.inputWhiteEnabled || this.state.inputBlackEnabled 136 | } 137 | 138 | enableSquareSelect(eventType = POINTER_EVENTS.pointerdown, eventHandler) { 139 | if (!this.squareSelectListener) { 140 | this.squareSelectListener = function (e) { 141 | const square = e.target.getAttribute("data-square") 142 | eventHandler({ 143 | eventType: e.type, 144 | event: e, 145 | chessboard: this, 146 | square: square 147 | }) 148 | } 149 | } 150 | this.context.addEventListener(eventType, this.squareSelectListener) 151 | this.state.squareSelectEnabled = true 152 | this.view.visualizeInputState() 153 | } 154 | 155 | disableSquareSelect(eventType) { 156 | this.context.removeEventListener(eventType, this.squareSelectListener) 157 | this.squareSelectListener = undefined 158 | this.state.squareSelectEnabled = false 159 | this.view.visualizeInputState() 160 | } 161 | 162 | isSquareSelectEnabled() { 163 | return this.state.squareSelectEnabled 164 | } 165 | 166 | addExtension(extensionClass, props) { 167 | if (this.getExtension(extensionClass)) { 168 | throw Error("extension \"" + extensionClass.name + "\" already added") 169 | } 170 | this.extensions.push(new extensionClass(this, props)) 171 | } 172 | 173 | getExtension(extensionClass) { 174 | for (const extension of this.extensions) { 175 | if (extension instanceof extensionClass) { 176 | return extension 177 | } 178 | } 179 | return null 180 | } 181 | 182 | destroy() { 183 | this.state.invokeExtensionPoints(EXTENSION_POINT.destroy) 184 | this.positionAnimationsQueue.destroy() 185 | this.view.destroy() 186 | this.view = undefined 187 | this.state = undefined 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/extensions/accessibility/Accessibility.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 7 | import {COLOR, INPUT_EVENT_TYPE} from "../../Chessboard.js" 8 | import {piecesTranslations, renderPieceTitle} from "./I18n.js" 9 | import {Utils} from "../../lib/Utils.js" 10 | 11 | const translations = { 12 | de: { 13 | chessboard: "Schachbrett", 14 | pieces_lists: "Figurenlisten", 15 | board_as_table: "Schachbrett als Tabelle", 16 | move_piece: "Figur bewegen", 17 | from: "Zug von", 18 | to: "Zug nach", 19 | move: "Zug ausführen", 20 | input_white_enabled: "Eingabe Weiß aktiviert", 21 | input_black_enabled: "Eingabe Schwarz aktiviert", 22 | input_disabled: "Eingabe deaktiviert", 23 | pieces: "Figuren", 24 | }, 25 | en: { 26 | chessboard: "Chessboard", 27 | pieces_lists: "Pieces lists", 28 | board_as_table: "Chessboard as table", 29 | move_piece: "Move piece", 30 | from: "Move from", 31 | to: "Move to", 32 | move: "Make move", 33 | input_white_enabled: "Input white enabled", 34 | input_black_enabled: "Input black enabled", 35 | input_disabled: "Input disabled", 36 | pieces: "Pieces" 37 | } 38 | } 39 | 40 | export class Accessibility extends Extension { 41 | constructor(chessboard, props) { 42 | super(chessboard) 43 | this.props = { 44 | language: navigator.language.substring(0, 2).toLowerCase(), // supports "de" and "en" for now, used for pieces naming 45 | brailleNotationInAlt: true, // show the braille notation of the position in the alt attribute of the SVG image 46 | movePieceForm: true, // display a form to move a piece (from, to, move) 47 | boardAsTable: true, // display the board additionally as HTML table 48 | piecesAsList: true, // display the pieces additionally as List 49 | visuallyHidden: true // hide all those extra outputs visually but keep them accessible for screen readers and braille displays 50 | } 51 | Object.assign(this.props, props) 52 | if (this.props.language !== "de" && this.props.language !== "en") { 53 | this.props.language = "en" 54 | } 55 | this.lang = this.props.language 56 | this.tPieces = piecesTranslations[this.lang] 57 | this.t = translations[this.lang] 58 | this.components = [] 59 | if(this.props.movePieceForm || this.props.boardAsTable || this.props.piecesAsList) { 60 | const container = document.createElement("div") 61 | container.classList.add("cm-chessboard-accessibility") 62 | this.chessboard.context.appendChild(container) 63 | if(this.props.visuallyHidden) { 64 | container.classList.add("visually-hidden") 65 | } 66 | if (this.props.movePieceForm) { 67 | this.components.push(new MovePieceForm(container, this)) 68 | } 69 | if (this.props.boardAsTable) { 70 | this.components.push(new BoardAsTable(container, this)) 71 | } 72 | if (this.props.piecesAsList) { 73 | this.components.push(new PiecesAsList(container, this)) 74 | } 75 | } 76 | if (this.props.brailleNotationInAlt) { 77 | this.components.push(new BrailleNotationInAlt(this)) 78 | } 79 | } 80 | } 81 | 82 | class BrailleNotationInAlt { 83 | constructor(extension) { 84 | this.extension = extension 85 | extension.registerExtensionPoint(EXTENSION_POINT.positionChanged, () => { 86 | this.redraw() 87 | }) 88 | } 89 | 90 | redraw() { 91 | const pieces = this.extension.chessboard.state.position.getPieces() 92 | let listW = piecesTranslations[this.extension.lang].colors.w.toUpperCase() + ":" 93 | let listB = piecesTranslations[this.extension.lang].colors.b.toUpperCase() + ":" 94 | for (const piece of pieces) { 95 | const pieceName = piece.type === "p" ? "" : piecesTranslations[this.extension.lang].pieces[piece.type].toUpperCase() 96 | if (piece.color === "w") { 97 | listW += " " + pieceName + piece.position 98 | } else { 99 | listB += " " + pieceName + piece.position 100 | } 101 | } 102 | const altText = `${listW} 103 | ${listB}` 104 | this.extension.chessboard.view.svg.setAttribute("alt", altText) 105 | } 106 | } 107 | 108 | class MovePieceForm { 109 | constructor(container, extension) { 110 | this.chessboard = extension.chessboard 111 | this.movePieceFormContainer = Utils.createDomElement(` 112 |
113 |

${extension.t.move_piece}

114 |
115 | 116 | 117 | 118 | 119 | 120 |
121 |
`) 122 | this.form = this.movePieceFormContainer.querySelector("form") 123 | this.inputFrom = this.form.querySelector(".input-from") 124 | this.inputTo = this.form.querySelector(".input-to") 125 | this.moveButton = this.form.querySelector(".button-move") 126 | this.form.addEventListener("submit", (evt) => { 127 | evt.preventDefault() 128 | if (this.chessboard.state.moveInputCallback({ 129 | chessboard: this.chessboard, 130 | type: INPUT_EVENT_TYPE.validateMoveInput, 131 | squareFrom: this.inputFrom.value, 132 | squareTo: this.inputTo.value 133 | })) { 134 | this.chessboard.movePiece(this.inputFrom.value, this.inputTo.value, 135 | true).then(() => { 136 | this.inputFrom.value = "" 137 | this.inputTo.value = "" 138 | }) 139 | } 140 | }) 141 | container.appendChild(this.movePieceFormContainer) 142 | extension.registerExtensionPoint(EXTENSION_POINT.moveInputToggled, () => { 143 | this.redraw() 144 | }) 145 | } 146 | 147 | redraw() { 148 | if (this.inputFrom) { 149 | if (this.chessboard.state.inputWhiteEnabled || this.chessboard.state.inputBlackEnabled) { 150 | this.inputFrom.disabled = false 151 | this.inputTo.disabled = false 152 | this.moveButton.disabled = false 153 | } else { 154 | this.inputFrom.disabled = true 155 | this.inputTo.disabled = true 156 | this.moveButton.disabled = true 157 | } 158 | } 159 | } 160 | } 161 | 162 | class BoardAsTable { 163 | constructor(container, extension) { 164 | this.extension = extension 165 | this.chessboard = extension.chessboard 166 | this.boardAsTableContainer = Utils.createDomElement(`

${extension.t.board_as_table}

`) 167 | this.boardAsTable = this.boardAsTableContainer.querySelector(".table") 168 | container.appendChild(this.boardAsTableContainer) 169 | extension.registerExtensionPoint(EXTENSION_POINT.positionChanged, () => { 170 | this.redraw() 171 | }) 172 | extension.registerExtensionPoint(EXTENSION_POINT.boardChanged, () => { 173 | this.redraw() 174 | }) 175 | } 176 | 177 | redraw() { 178 | const squares = this.chessboard.state.position.squares.slice() 179 | const ranks = ["a", "b", "c", "d", "e", "f", "g", "h"] 180 | const files = ["8", "7", "6", "5", "4", "3", "2", "1"] 181 | if (this.chessboard.state.orientation === COLOR.black) { 182 | ranks.reverse() 183 | files.reverse() 184 | squares.reverse() 185 | } 186 | let html = `` 187 | for (const rank of ranks) { 188 | html += `` 189 | } 190 | html += "" 191 | for (let x = 7; x >= 0; x--) { 192 | html += `` 193 | for (let y = 0; y < 8; y++) { 194 | const pieceCode = squares[y % 8 + x * 8] 195 | let color, name 196 | if (pieceCode) { 197 | color = pieceCode.charAt(0) 198 | name = pieceCode.charAt(1) 199 | html += `` 200 | } else { 201 | html += `` 202 | } 203 | } 204 | html += "" 205 | } 206 | html += "
${rank}
${files[7 - x]}${renderPieceTitle(this.extension.lang, name, color)}
" 207 | this.boardAsTable.innerHTML = html 208 | } 209 | } 210 | 211 | class PiecesAsList { 212 | constructor(container, extension) { 213 | this.extension = extension 214 | this.chessboard = extension.chessboard 215 | this.piecesListContainer = Utils.createDomElement(`

${extension.t.pieces_lists}

`) 216 | this.piecesList = this.piecesListContainer.querySelector(".list") 217 | container.appendChild(this.piecesListContainer) 218 | extension.registerExtensionPoint(EXTENSION_POINT.positionChanged, () => { 219 | this.redraw() 220 | }) 221 | } 222 | 223 | redraw() { 224 | const pieces = this.chessboard.state.position.getPieces() 225 | let listW = "" 226 | let listB = "" 227 | for (const piece of pieces) { 228 | if (piece.color === "w") { 229 | listW += `
  • ${renderPieceTitle(this.extension.lang, piece.type)} ${piece.position}
  • ` 230 | } else { 231 | listB += `
  • ${renderPieceTitle(this.extension.lang, piece.type)} ${piece.position}
  • ` 232 | } 233 | } 234 | this.piecesList.innerHTML = ` 235 |

    ${this.extension.t.pieces} ${this.extension.tPieces.colors_long.w}

    236 | 237 |

    ${this.extension.t.pieces} ${this.extension.tPieces.colors_long.b}

    238 | ` 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/extensions/accessibility/I18n.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | export const piecesTranslations = { 8 | en: { 9 | colors: { 10 | w: "w", b: "b" 11 | }, 12 | colors_long: { 13 | w: "White", b: "Black" 14 | }, 15 | pieces: { 16 | p: "p", n: "n", b: "b", r: "r", q: "q", k: "k" 17 | }, 18 | pieces_long: { 19 | p: "Pawn", n: "Knight", b: "Bishop", r: "Rook", q: "Queen", k: "King" 20 | } 21 | }, 22 | de: { 23 | colors: { 24 | w: "w", b: "s" 25 | }, 26 | colors_long: { 27 | w: "Weiß", b: "Schwarz" 28 | }, 29 | pieces: { 30 | p: "b", n: "s", b: "l", r: "t", q: "d", k: "k" 31 | }, 32 | pieces_long: { 33 | p: "Bauer", n: "Springer", b: "Läufer", r: "Turm", q: "Dame", k: "König" 34 | } 35 | } 36 | } 37 | 38 | export function renderPieceTitle(lang, name, color = undefined) { 39 | let title = piecesTranslations[lang].pieces_long[name] 40 | if (color) { 41 | title += " " + piecesTranslations[lang].colors_long[color] 42 | } 43 | return title 44 | } 45 | -------------------------------------------------------------------------------- /src/extensions/arrows/Arrows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Authors and copyright: Barak Michener (@barakmich) and Stefan Haack (@shaack) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 8 | import {Svg} from "../../lib/Svg.js" 9 | import {Utils} from "../../lib/Utils.js" 10 | 11 | export const ARROW_TYPE = { 12 | default: {class: "arrow-success"}, 13 | success: {class: "arrow-success"}, 14 | warning: {class: "arrow-warning"}, 15 | info: {class: "arrow-info"}, 16 | danger: {class: "arrow-danger"} 17 | } 18 | 19 | export class Arrows extends Extension { 20 | 21 | /** @constructor */ 22 | constructor(chessboard, props = {}) { 23 | super(chessboard) 24 | this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, () => { 25 | this.onRedrawBoard() 26 | }) 27 | this.props = { 28 | sprite: "extensions/arrows/arrows.svg", 29 | slice: "arrowDefault", 30 | headSize: 7, 31 | offsetFrom: 0, 32 | offsetTo: 0.55 33 | } 34 | Object.assign(this.props, props) 35 | if (this.chessboard.props.assetsCache) { 36 | this.chessboard.view.cacheSpriteToDiv("cm-chessboard-arrows", this.getSpriteUrl()) 37 | } 38 | chessboard.addArrow = this.addArrow.bind(this) 39 | chessboard.getArrows = this.getArrows.bind(this) 40 | chessboard.removeArrows = this.removeArrows.bind(this) 41 | this.arrowGroup = Svg.addElement(chessboard.view.markersTopLayer, "g", {class: "arrows"}) 42 | this.arrows = [] 43 | } 44 | 45 | onRedrawBoard() { 46 | while (this.arrowGroup.firstChild) { 47 | this.arrowGroup.removeChild(this.arrowGroup.firstChild) 48 | } 49 | this.arrows.forEach((arrow) => { 50 | this.drawArrow(arrow) 51 | }) 52 | } 53 | 54 | drawArrow(arrow) { 55 | const view = this.chessboard.view 56 | const arrowsGroup = Svg.addElement(this.arrowGroup, "g") 57 | arrowsGroup.setAttribute("data-arrow", arrow.from + arrow.to) 58 | arrowsGroup.setAttribute("class", "arrow " + arrow.type.class) 59 | const ptFrom = view.squareToPoint(arrow.from) 60 | const ptTo = view.squareToPoint(arrow.to) 61 | const spriteUrl = this.chessboard.props.assetsCache ? "" : this.getSpriteUrl() 62 | const defs = Svg.addElement(arrowsGroup, "defs") 63 | const id = "arrow-" + arrow.from + arrow.to 64 | const marker = Svg.addElement(defs, "marker", { 65 | id: id, 66 | markerWidth: this.props.headSize, 67 | markerHeight: this.props.headSize, 68 | refX: 20, 69 | refY: 20, 70 | viewBox: "0 0 40 40", 71 | orient: "auto", 72 | class: "arrow-head", 73 | }) 74 | 75 | Svg.addElement(marker, "use", { 76 | href: `${spriteUrl}#${this.props.slice}`, 77 | }) 78 | 79 | // Compute centers of start and end squares 80 | const cx1 = ptFrom.x + view.squareWidth / 2 81 | const cy1 = ptFrom.y + view.squareHeight / 2 82 | const cx2 = ptTo.x + view.squareWidth / 2 83 | const cy2 = ptTo.y + view.squareHeight / 2 84 | 85 | // Offset the line so it starts/ends at the edge of an invisible circle inside each square 86 | const dx = cx2 - cx1 87 | const dy = cy2 - cy1 88 | const len = Math.hypot(dx, dy) || 1 89 | const ux = dx / len 90 | const uy = dy / len 91 | // compute radii from props: factor relative to half of the square size 92 | const halfMin = Math.min(view.squareWidth, view.squareHeight) / 2 93 | const clamp01 = (v) => Math.max(0, Math.min(1, v)) 94 | const rFrom = halfMin * clamp01(this.props.offsetFrom) 95 | const rTo = halfMin * clamp01(this.props.offsetTo) 96 | const x1 = cx1 + ux * rFrom 97 | const y1 = cy1 + uy * rFrom 98 | const x2 = cx2 - ux * rTo 99 | const y2 = cy2 - uy * rTo 100 | 101 | const width = ((view.scalingX + view.scalingY) / 2) * 8 102 | let lineFill = Svg.addElement(arrowsGroup, "line") 103 | lineFill.setAttribute('x1', x1.toString()) 104 | lineFill.setAttribute('x2', x2.toString()) 105 | lineFill.setAttribute('y1', y1.toString()) 106 | lineFill.setAttribute('y2', y2.toString()) 107 | lineFill.setAttribute('class', 'arrow-line') 108 | lineFill.setAttribute("marker-end", "url(#" + id + ")") 109 | lineFill.setAttribute('stroke-width', width + "px") 110 | } 111 | 112 | addArrow(type, from, to) { 113 | this.arrows.push(new Arrow(from, to, type)) 114 | this.chessboard.view.redrawBoard() 115 | } 116 | 117 | getArrows(type = undefined, from = undefined, to = undefined) { 118 | let arrows = [] 119 | this.arrows.forEach((arrow) => { 120 | if (arrow.matches(from, to, type)) { 121 | arrows.push(arrow) 122 | } 123 | }) 124 | return arrows 125 | } 126 | 127 | removeArrows(type = undefined, from = undefined, to = undefined) { 128 | this.arrows = this.arrows.filter((arrow) => !arrow.matches(from, to, type)) 129 | this.chessboard.view.redrawBoard() 130 | } 131 | 132 | getSpriteUrl() { 133 | if(Utils.isAbsoluteUrl(this.props.sprite)) { 134 | return this.props.sprite 135 | } else { 136 | return this.chessboard.props.assetsUrl + this.props.sprite 137 | } 138 | } 139 | } 140 | 141 | class Arrow { 142 | constructor(from, to, type) { 143 | this.from = from 144 | this.to = to 145 | this.type = type 146 | } 147 | 148 | matches(from = undefined, to = undefined, type = undefined) { 149 | if (from && from !== this.from) { 150 | return false 151 | } 152 | if (to && to !== this.to) { 153 | return false 154 | } 155 | return !(type && type !== this.type) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/extensions/auto-border-none/AutoBorderNone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 7 | 8 | export class AutoBorderNone extends Extension { 9 | constructor(chessboard, props = {}) { 10 | super(chessboard) 11 | this.props = { 12 | chessboardBorderType: chessboard.props.style.borderType, 13 | borderNoneBelow: 540 // pixels width of the board, where the border is set to none 14 | } 15 | Object.assign(this.props, props) 16 | this.registerExtensionPoint(EXTENSION_POINT.beforeRedrawBoard, this.extensionPointBeforeRedrawBoard.bind(this)) 17 | } 18 | extensionPointBeforeRedrawBoard() { 19 | let newBorderType 20 | if(this.chessboard.view.width < this.props.borderNoneBelow){ 21 | newBorderType = "none" 22 | } else { 23 | newBorderType = this.props.chessboardBorderType 24 | } 25 | if(newBorderType !== this.chessboard.props.style.borderType) { 26 | this.chessboard.props.style.borderType = newBorderType 27 | this.chessboard.view.updateMetrics() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/extensions/html-layer/HtmlLayer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension} from "../../model/Extension.js" 7 | 8 | export class HtmlLayer extends Extension { 9 | /** @constructor */ 10 | constructor(chessboard) { 11 | super(chessboard) 12 | chessboard.addHtmlLayer = this.addHtmlLayer.bind(this) 13 | chessboard.removeHtmlLayer = this.removeHtmlLayer.bind(this) 14 | } 15 | addHtmlLayer(html) { 16 | const layer = document.createElement("div") 17 | this.chessboard.context.appendChild(layer) 18 | this.chessboard.context.style.position = "relative" 19 | layer.classList.add("html-layer") 20 | layer.style.position = "absolute" 21 | layer.style.top = "0" 22 | layer.style.left = "0" 23 | layer.style.bottom = "0" 24 | layer.style.right = "0" 25 | layer.innerHTML = html 26 | return layer 27 | } 28 | removeHtmlLayer(layer) { 29 | this.chessboard.context.removeChild(layer) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/extensions/markers/Markers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 7 | import {Svg} from "../../lib/Svg.js" 8 | import {INPUT_EVENT_TYPE} from "../../Chessboard.js" 9 | import {Utils} from "../../lib/Utils.js" 10 | 11 | export const MARKER_TYPE = { 12 | frame: {class: "marker-frame", slice: "markerFrame"}, 13 | framePrimary: {class: "marker-frame-primary", slice: "markerFrame"}, 14 | frameDanger: {class: "marker-frame-danger", slice: "markerFrame"}, 15 | circle: {class: "marker-circle", slice: "markerCircle"}, 16 | circlePrimary: {class: "marker-circle-primary", slice: "markerCircle"}, 17 | circleDanger: {class: "marker-circle-danger", slice: "markerCircle"}, 18 | circleDangerFilled: {class: "marker-circle-danger-filled", slice: "markerCircleFilled"}, 19 | square: {class: "marker-square", slice: "markerSquare"}, 20 | dot: {class: "marker-dot", slice: "markerDot", position: 'above'}, 21 | bevel: {class: "marker-bevel", slice: "markerBevel"} 22 | } 23 | 24 | export class Markers extends Extension { 25 | 26 | /** @constructor */ 27 | constructor(chessboard, props = {}) { 28 | super(chessboard) 29 | this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, () => { 30 | this.onRedrawBoard() 31 | }) 32 | this.props = { 33 | autoMarkers: MARKER_TYPE.frame, // set to `null` to disable autoMarkers 34 | sprite: "extensions/markers/markers.svg" // the sprite file of the markers 35 | } 36 | Object.assign(this.props, props) 37 | if (chessboard.props.assetsCache) { 38 | chessboard.view.cacheSpriteToDiv("cm-chessboard-markers", this.getSpriteUrl()) 39 | } 40 | chessboard.addMarker = this.addMarker.bind(this) 41 | chessboard.getMarkers = this.getMarkers.bind(this) 42 | chessboard.removeMarkers = this.removeMarkers.bind(this) 43 | chessboard.addLegalMovesMarkers = this.addLegalMovesMarkers.bind(this) 44 | chessboard.removeLegalMovesMarkers = this.removeLegalMovesMarkers.bind(this) 45 | this.markerGroupDown = Svg.addElement(chessboard.view.markersLayer, "g", {class: "markers"}) 46 | this.markerGroupUp = Svg.addElement(chessboard.view.markersTopLayer, "g", {class: "markers"}) 47 | this.markers = [] 48 | if (this.props.autoMarkers) { 49 | Object.assign(this.props.autoMarkers, this.props.autoMarkers) 50 | this.registerExtensionPoint(EXTENSION_POINT.moveInput, (event) => { 51 | this.drawAutoMarkers(event) 52 | }) 53 | } 54 | } 55 | 56 | drawAutoMarkers(event) { 57 | if(event.type !== INPUT_EVENT_TYPE.moveInputFinished) { 58 | this.removeMarkers(this.props.autoMarkers) 59 | } 60 | if (event.type === INPUT_EVENT_TYPE.moveInputStarted && 61 | !event.moveInputCallbackResult) { 62 | return 63 | } 64 | if (event.type === INPUT_EVENT_TYPE.moveInputStarted || 65 | event.type === INPUT_EVENT_TYPE.movingOverSquare) { 66 | if (event.squareFrom) { 67 | this.addMarker(this.props.autoMarkers, event.squareFrom) 68 | } 69 | if (event.squareTo) { 70 | this.addMarker(this.props.autoMarkers, event.squareTo) 71 | } 72 | } 73 | } 74 | 75 | onRedrawBoard() { 76 | while (this.markerGroupUp.firstChild) { 77 | this.markerGroupUp.removeChild(this.markerGroupUp.firstChild) 78 | } 79 | while (this.markerGroupDown.firstChild) { 80 | this.markerGroupDown.removeChild(this.markerGroupDown.firstChild) 81 | } 82 | this.markers.forEach((marker) => { 83 | this.drawMarker(marker) 84 | } 85 | ) 86 | } 87 | 88 | addLegalMovesMarkers(moves) { 89 | for (const move of moves) { 90 | if (move.promotion && move.promotion !== "q") { 91 | continue 92 | } 93 | if (this.chessboard.getPiece(move.to)) { 94 | this.chessboard.addMarker(MARKER_TYPE.bevel, move.to) 95 | } else { 96 | this.chessboard.addMarker(MARKER_TYPE.dot, move.to) 97 | } 98 | } 99 | } 100 | 101 | removeLegalMovesMarkers() { 102 | this.chessboard.removeMarkers(MARKER_TYPE.bevel) 103 | this.chessboard.removeMarkers(MARKER_TYPE.dot) 104 | } 105 | 106 | drawMarker(marker) { 107 | let markerGroup 108 | if (marker.type.position === 'above') { 109 | markerGroup = Svg.addElement(this.markerGroupUp, "g") 110 | } else { 111 | markerGroup = Svg.addElement(this.markerGroupDown, "g") 112 | } 113 | markerGroup.setAttribute("data-square", marker.square) 114 | const point = this.chessboard.view.squareToPoint(marker.square) 115 | const transform = (this.chessboard.view.svg.createSVGTransform()) 116 | transform.setTranslate(point.x, point.y) 117 | markerGroup.transform.baseVal.appendItem(transform) 118 | const spriteUrl = this.chessboard.props.assetsCache ? "" : this.getSpriteUrl() 119 | const markerUse = Svg.addElement(markerGroup, "use", 120 | {href: `${spriteUrl}#${marker.type.slice}`, class: "marker " + marker.type.class}) 121 | const transformScale = (this.chessboard.view.svg.createSVGTransform()) 122 | transformScale.setScale(this.chessboard.view.scalingX, this.chessboard.view.scalingY) 123 | markerUse.transform.baseVal.appendItem(transformScale) 124 | return markerGroup 125 | } 126 | 127 | addMarker(type, square) { 128 | if (typeof type === "string" || typeof square === "object") { // todo remove 2022-12-01 129 | console.error("changed the signature of `addMarker` to `(type, square)` with v5.1.x") 130 | return 131 | } 132 | this.markers.push(new Marker(square, type)) 133 | this.onRedrawBoard() 134 | } 135 | 136 | getMarkers(type = undefined, square = undefined) { 137 | if (typeof type === "string" || typeof square === "object") { // todo remove 2022-12-01 138 | console.error("changed the signature of `getMarkers` to `(type, square)` with v5.1.x") 139 | return 140 | } 141 | let markersFound = [] 142 | this.markers.forEach((marker) => { 143 | if (marker.matches(square, type)) { 144 | markersFound.push(marker) 145 | } 146 | }) 147 | return markersFound 148 | } 149 | 150 | removeMarkers(type = undefined, square = undefined) { 151 | if (typeof type === "string" || typeof square === "object") { // todo remove 2022-12-01 152 | console.error("changed the signature of `removeMarkers` to `(type, square)` with v5.1.x") 153 | return 154 | } 155 | this.markers = this.markers.filter((marker) => !marker.matches(square, type)) 156 | this.onRedrawBoard() 157 | } 158 | 159 | getSpriteUrl() { 160 | if(Utils.isAbsoluteUrl(this.props.sprite)) { 161 | return this.props.sprite 162 | } else { 163 | return this.chessboard.props.assetsUrl + this.props.sprite 164 | } 165 | } 166 | } 167 | 168 | class Marker { 169 | constructor(square, type) { 170 | this.square = square 171 | this.type = type 172 | } 173 | 174 | matches(square = undefined, type = undefined) { 175 | if (!type && !square) { 176 | return true 177 | } else if (!type) { 178 | if (square === this.square) { 179 | return true 180 | } 181 | } else if (!square) { 182 | if (this.type === type) { 183 | return true 184 | } 185 | } else if (this.type === type && square === this.square) { 186 | return true 187 | } 188 | return false 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/extensions/markers/README.md: -------------------------------------------------------------------------------- 1 | # cm-chessboard Markers extension 2 | 3 | ## API 4 | 5 | ### addMarker(type, square) 6 | 7 | Adds a marker on a square. 8 | 9 | Default types are: `MARKER_TYPE.frame`, `MARKER_TYPE.square`, `MARKER_TYPE.dot`, `MARKER_TYPE.circle` exported 10 | by `Chessboard.js`. 11 | 12 | [Example for **addMarker**, **getMarkers** and 13 | **removeMarkers**](https://shaack.com/projekte/cm-chessboard/examples/extensions/markers-extension.html) 14 | 15 | ### getMarkers(type = undefined, square = undefined) 16 | 17 | Returns the board's markers as an array. 18 | 19 | Only set type, to get all markers of a type on the board. Set type to `undefined`, to get markers of all types on a 20 | square. 21 | 22 | Set both to `undefined` (or don't set them at all) to get all markers on the board. 23 | 24 | ### removeMarkers(type = undefined, square = undefined) 25 | 26 | Removes markers from the board. 27 | 28 | Only set `type` to remove all markers of `type` from the board. Set `type` to `undefined`, to remove all types 29 | of markers from a square. Call without parameters to remove all markers from the board. 30 | 31 | ## Create your own custom markers 32 | 33 | Just create an object like `const myMarker = {class: "markerCssClass", slice: "markerSliceId"}`, where `class` is the 34 | css class of the marker for styling and `slice` is the `id` in `sprite.svg`. 35 | 36 | ### Example 37 | 38 | The markerCircle is defined in the SVG as a circle with a radius of 18: 39 | 40 | ```svg 41 | 42 | 43 | 44 | 45 | ``` 46 | 47 | Has this CSS, where stroke color, width and opacity are defined: 48 | 49 | ```css 50 | marker.marker-circle-red { 51 | stroke: #aa0000; 52 | stroke-width: 3px; 53 | opacity: 0.4; 54 | } 55 | ``` 56 | 57 | And is used like this in your JavaScript: 58 | 59 | ```js 60 | const myMarkerType = {class: "marker-circle-red", slice: "markerCircle"} 61 | // add 62 | chessboard.addMarker(myMarkerType, "e4") 63 | // remove a specific marker 64 | chessboard.removeMarkers(myMarkerType, "e4") 65 | // remove all "myMarkerType" 66 | chessboard.removeMarkers(myMarkerType) 67 | // remove all markers 68 | chessboard.removeMarkers() 69 | ``` 70 | -------------------------------------------------------------------------------- /src/extensions/persistence/Persistence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js"; 7 | 8 | export class Persistence extends Extension { 9 | constructor(chessboard, props) { 10 | super(chessboard) 11 | console.warn("The Persistence extension is work in progress, don't use it in production.") 12 | this.props = props 13 | this.registerExtensionPoint(EXTENSION_POINT.positionChanged, this.savePosition.bind(this)) 14 | this.loadPosition() 15 | } 16 | 17 | savePosition() { 18 | localStorage.setItem("chessboard", JSON.stringify(this.chessboard.getPosition())) 19 | } 20 | 21 | loadPosition() { 22 | const position = localStorage.getItem("chessboard") 23 | if (position) { 24 | this.chessboard.setPosition(JSON.parse(position)) 25 | } else { 26 | this.chessboard.setPosition(this.props.initialPosition) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/extensions/promotion-dialog/PromotionDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 7 | import {COLOR, PIECE} from "../../Chessboard.js" 8 | import {Svg} from "../../lib/Svg.js" 9 | import {Utils} from "../../lib/Utils.js" 10 | 11 | const DISPLAY_STATE = { 12 | hidden: "hidden", 13 | displayRequested: "displayRequested", 14 | shown: "shown" 15 | } 16 | 17 | export const PROMOTION_DIALOG_RESULT_TYPE = { 18 | pieceSelected: "pieceSelected", 19 | canceled: "canceled" 20 | } 21 | 22 | export class PromotionDialog extends Extension { 23 | 24 | /** @constructor */ 25 | constructor(chessboard) { 26 | super(chessboard) 27 | this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, this.extensionPointRedrawBoard.bind(this)) 28 | chessboard.showPromotionDialog = this.showPromotionDialog.bind(this) 29 | chessboard.isPromotionDialogShown = this.isPromotionDialogShown.bind(this) 30 | this.promotionDialogGroup = Svg.addElement(chessboard.view.interactiveTopLayer, "g", {class: "promotion-dialog-group"}) 31 | this.state = { 32 | displayState: DISPLAY_STATE.hidden, 33 | callback: null, 34 | dialogParams: { 35 | square: null, 36 | color: null 37 | } 38 | } 39 | } 40 | 41 | // public (chessboard.showPromotionDialog) 42 | showPromotionDialog(square, color, callback) { 43 | this.state.dialogParams.square = square 44 | this.state.dialogParams.color = color 45 | this.state.callback = callback 46 | this.setDisplayState(DISPLAY_STATE.displayRequested) 47 | setTimeout(() => { 48 | this.chessboard.view.positionsAnimationTask.then(() => { 49 | this.setDisplayState(DISPLAY_STATE.shown) 50 | }) 51 | } 52 | ) 53 | } 54 | 55 | // public (chessboard.isPromotionDialogShown) 56 | isPromotionDialogShown() { 57 | return this.state.displayState === DISPLAY_STATE.shown || 58 | this.state.displayState === DISPLAY_STATE.displayRequested 59 | } 60 | 61 | // private 62 | extensionPointRedrawBoard() { 63 | this.redrawDialog() 64 | } 65 | 66 | drawPieceButton(piece, point) { 67 | const squareWidth = this.chessboard.view.squareWidth 68 | const squareHeight = this.chessboard.view.squareHeight 69 | Svg.addElement(this.promotionDialogGroup, 70 | "rect", { 71 | x: point.x, y: point.y, width: squareWidth, height: squareHeight, 72 | class: "promotion-dialog-button", 73 | "data-piece": piece 74 | }) 75 | this.chessboard.view.drawPiece(this.promotionDialogGroup, piece, point) 76 | } 77 | 78 | redrawDialog() { 79 | while (this.promotionDialogGroup.firstChild) { 80 | this.promotionDialogGroup.removeChild(this.promotionDialogGroup.firstChild) 81 | } 82 | if (this.state.displayState === DISPLAY_STATE.shown) { 83 | const squareWidth = this.chessboard.view.squareWidth 84 | const squareHeight = this.chessboard.view.squareHeight 85 | const squareCenterPoint = this.chessboard.view.squareToPoint(this.state.dialogParams.square) 86 | squareCenterPoint.x = squareCenterPoint.x + squareWidth / 2 87 | squareCenterPoint.y = squareCenterPoint.y + squareHeight / 2 88 | let turned = false 89 | const rank = parseInt(this.state.dialogParams.square.charAt(1), 10) 90 | if (this.chessboard.getOrientation() === COLOR.white && rank < 5 || 91 | this.chessboard.getOrientation() === COLOR.black && rank >= 5) { 92 | turned = true 93 | } 94 | const offsetY = turned ? -4 * squareHeight : 0 95 | const offsetX = squareCenterPoint.x + squareWidth > this.chessboard.view.width ? -squareWidth : 0 96 | Svg.addElement(this.promotionDialogGroup, 97 | "rect", { 98 | x: squareCenterPoint.x + offsetX, 99 | y: squareCenterPoint.y + offsetY, 100 | width: squareWidth, 101 | height: squareHeight * 4, 102 | class: "promotion-dialog" 103 | }) 104 | const dialogParams = this.state.dialogParams 105 | if (turned) { 106 | this.drawPieceButton(PIECE[dialogParams.color + "q"], { 107 | x: squareCenterPoint.x + offsetX, 108 | y: squareCenterPoint.y - squareHeight 109 | }) 110 | this.drawPieceButton(PIECE[dialogParams.color + "r"], { 111 | x: squareCenterPoint.x + offsetX, 112 | y: squareCenterPoint.y - squareHeight * 2 113 | }) 114 | this.drawPieceButton(PIECE[dialogParams.color + "b"], { 115 | x: squareCenterPoint.x + offsetX, 116 | y: squareCenterPoint.y - squareHeight * 3 117 | }) 118 | this.drawPieceButton(PIECE[dialogParams.color + "n"], { 119 | x: squareCenterPoint.x + offsetX, 120 | y: squareCenterPoint.y - squareHeight * 4 121 | }) 122 | } else { 123 | this.drawPieceButton(PIECE[dialogParams.color + "q"], { 124 | x: squareCenterPoint.x + offsetX, 125 | y: squareCenterPoint.y 126 | }) 127 | this.drawPieceButton(PIECE[dialogParams.color + "r"], { 128 | x: squareCenterPoint.x + offsetX, 129 | y: squareCenterPoint.y + squareHeight 130 | }) 131 | this.drawPieceButton(PIECE[dialogParams.color + "b"], { 132 | x: squareCenterPoint.x + offsetX, 133 | y: squareCenterPoint.y + squareHeight * 2 134 | }) 135 | this.drawPieceButton(PIECE[dialogParams.color + "n"], { 136 | x: squareCenterPoint.x + offsetX, 137 | y: squareCenterPoint.y + squareHeight * 3 138 | }) 139 | } 140 | } 141 | } 142 | 143 | promotionDialogOnClickPiece(event) { 144 | if (event.button !== 2) { 145 | if (event.target.dataset.piece) { 146 | if(this.state.callback) { 147 | this.state.callback({ 148 | type: PROMOTION_DIALOG_RESULT_TYPE.pieceSelected, 149 | square: this.state.dialogParams.square, 150 | piece: event.target.dataset.piece 151 | }) 152 | } 153 | this.setDisplayState(DISPLAY_STATE.hidden) 154 | } else { 155 | this.promotionDialogOnCancel(event) 156 | } 157 | } 158 | } 159 | 160 | promotionDialogOnCancel(event) { 161 | if (this.state.displayState === DISPLAY_STATE.shown) { 162 | event.preventDefault() 163 | this.setDisplayState(DISPLAY_STATE.hidden) 164 | if(this.state.callback) { 165 | this.state.callback({type: PROMOTION_DIALOG_RESULT_TYPE.canceled}) 166 | } 167 | } 168 | } 169 | 170 | contextMenu(event) { 171 | event.preventDefault() 172 | this.setDisplayState(DISPLAY_STATE.hidden) 173 | if(this.state.callback) { 174 | this.state.callback({type: PROMOTION_DIALOG_RESULT_TYPE.canceled}) 175 | } 176 | } 177 | 178 | setDisplayState(displayState) { 179 | this.state.displayState = displayState 180 | if (displayState === DISPLAY_STATE.shown) { 181 | this.clickDelegate = Utils.delegate(this.chessboard.view.svg, 182 | "pointerdown", 183 | "*", 184 | this.promotionDialogOnClickPiece.bind(this)) 185 | this.contextMenuListener = this.contextMenu.bind(this) 186 | this.chessboard.view.svg.addEventListener("contextmenu", this.contextMenuListener) 187 | } else if (displayState === DISPLAY_STATE.hidden) { 188 | this.clickDelegate.remove() 189 | this.chessboard.view.svg.removeEventListener("contextmenu", this.contextMenuListener) 190 | } 191 | this.redrawDialog() 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/extensions/right-click-annotator/RightClickAnnotator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extension: RightClickAnnotator 3 | * Combines Arrows and Markers to draw/toggle arrows and circle markers with right-click + modifiers. 4 | * Colors: 5 | * - Green: Right-click 6 | * - Blue: Alt + Right-click 7 | * - Red: Shift + Right-click 8 | * - Orange: Shift + Alt + Right-click 9 | * Redrawing the same arrow or marker removes it. 10 | */ 11 | import {Extension, EXTENSION_POINT} from "../../model/Extension.js" 12 | import {Arrows} from "../arrows/Arrows.js" 13 | import {Markers} from "../markers/Markers.js" 14 | import {Svg} from "../../lib/Svg.js" 15 | 16 | export const ARROW_TYPE = { 17 | success: { class: "arrow-success"}, 18 | warning: { class: "arrow-warning"}, 19 | info: { class: "arrow-info"}, 20 | danger: { class: "arrow-danger"} 21 | } 22 | 23 | export const MARKER_TYPE = { 24 | success: {class: "marker-circle-success", slice: "markerCircle"}, 25 | warning: {class: "marker-circle-warning", slice: "markerCircle"}, 26 | info: {class: "marker-circle-info", slice: "markerCircle"}, 27 | danger: {class: "marker-circle-danger", slice: "markerCircle"}, 28 | } 29 | 30 | export class RightClickAnnotator extends Extension { 31 | 32 | /** @constructor */ 33 | constructor(chessboard, props = {}) { 34 | super(chessboard) 35 | this.props = props || {} 36 | 37 | // Ensure Arrows and Markers extensions are available 38 | if (!this.chessboard.getExtension(Arrows)) { 39 | this.chessboard.addExtension(Arrows) 40 | } 41 | if (!this.chessboard.getExtension(Markers)) { 42 | this.chessboard.addExtension(Markers) 43 | } 44 | 45 | this.onContextMenu = this.onContextMenu.bind(this) 46 | this.onMouseDown = this.onMouseDown.bind(this) 47 | this.onMouseMove = this.onMouseMove.bind(this) 48 | this.onMouseUp = this.onMouseUp.bind(this) 49 | 50 | this.dragStart = undefined // {square, modifiers} 51 | this.previewActiveTo = undefined // cache last to-square for preview 52 | 53 | this.chessboard.context.addEventListener("contextmenu", this.onContextMenu) 54 | this.chessboard.context.addEventListener("mousedown", this.onMouseDown) 55 | this.chessboard.context.addEventListener("mousemove", this.onMouseMove) 56 | this.chessboard.context.addEventListener("mouseup", this.onMouseUp) 57 | this.chessboard.context.addEventListener("mouseleave", this.onMouseUp) 58 | 59 | this.registerExtensionPoint(EXTENSION_POINT.destroy, () => { 60 | this.chessboard.context.removeEventListener("contextmenu", this.onContextMenu) 61 | this.chessboard.context.removeEventListener("mousedown", this.onMouseDown) 62 | this.chessboard.context.removeEventListener("mousemove", this.onMouseMove) 63 | this.chessboard.context.removeEventListener("mouseup", this.onMouseUp) 64 | this.chessboard.context.removeEventListener("mouseleave", this.onMouseUp) 65 | }) 66 | 67 | // register public API 68 | this.chessboard.getAnnotations = this.getAnnotations.bind(this.chessboard) 69 | this.chessboard.setAnnotations = this.setAnnotations.bind(this.chessboard) 70 | } 71 | 72 | getAnnotations() { 73 | return { 74 | arrows: this.chessboard.getArrows(), 75 | markers: this.chessboard.getMarkers() 76 | } 77 | } 78 | 79 | setAnnotations(annotations) { 80 | this.chessboard.removeArrows() 81 | this.chessboard.removeMarkers() 82 | if (annotations.arrows) { 83 | for (const arrow of annotations.arrows) { 84 | this.chessboard.addArrow(arrow.type, arrow.from, arrow.to) 85 | } 86 | } 87 | if (annotations.markers) { 88 | for (const marker of annotations.markers) { 89 | this.chessboard.addMarker(marker.type, marker.square) 90 | } 91 | } 92 | } 93 | 94 | onContextMenu(event) { 95 | event.preventDefault() 96 | } 97 | 98 | onMouseDown(event) { 99 | // right button only 100 | if (event.button !== 2) { 101 | return 102 | } 103 | const square = this.findSquareFromEvent(event) 104 | if (!square) { 105 | return 106 | } 107 | this.dragStart = { 108 | square, 109 | modifiers: { 110 | alt: event.altKey, 111 | shift: event.shiftKey 112 | } 113 | } 114 | } 115 | 116 | onMouseUp(event) { 117 | // clear preview regardless of button, but only act on right-button release 118 | this.removePreviewArrow() 119 | const start = this.dragStart 120 | this.dragStart = undefined 121 | if (!start || event.button !== 2) { 122 | return 123 | } 124 | const endSquare = this.findSquareFromEvent(event) || start.square 125 | const colorKey = this.modifiersToColorKey(start.modifiers) 126 | const {arrowType, circleType} = this.typesForColorKey(colorKey) 127 | 128 | if (start.square && endSquare && start.square !== endSquare) { 129 | // toggle arrow 130 | const existing = this.chessboard.getArrows(arrowType, start.square, endSquare) 131 | if (existing && existing.length > 0) { 132 | this.chessboard.removeArrows(arrowType, start.square, endSquare) 133 | } else { 134 | this.chessboard.removeArrows(undefined, start.square, endSquare) 135 | this.chessboard.addArrow(arrowType, start.square, endSquare) 136 | } 137 | } else if (start.square) { 138 | // toggle marker on start square 139 | const existingMarkers = this.chessboard.getMarkers(circleType, start.square) 140 | if (existingMarkers && existingMarkers.length > 0) { 141 | this.chessboard.removeMarkers(circleType, start.square) 142 | } else { 143 | this.chessboard.removeMarkers(undefined, start.square) 144 | this.chessboard.addMarker(circleType, start.square) 145 | } 146 | } 147 | } 148 | 149 | findSquareFromEvent(event) { 150 | const target = /** @type {HTMLElement} */(event.target) 151 | if (!target) return undefined 152 | if (target.getAttribute && target.getAttribute("data-square")) { 153 | return target.getAttribute("data-square") 154 | } 155 | const el = target.closest && target.closest("[data-square]") 156 | return el ? el.getAttribute("data-square") : undefined 157 | } 158 | 159 | onMouseMove(event) { 160 | if (!this.dragStart) { 161 | return 162 | } 163 | // Only show preview for right-button drag if still pressed (best-effort); some browsers may not keep buttons state reliably 164 | // We rely mainly on our dragStart flag and clear on mouseup/mouseleave 165 | const toSquare = this.findSquareFromEvent(event) 166 | if (!toSquare || toSquare === this.dragStart.square) { 167 | return 168 | } 169 | if (this.previewActiveTo === toSquare) { 170 | return // no change 171 | } 172 | this.previewActiveTo = toSquare 173 | const colorKey = this.modifiersToColorKey(this.dragStart.modifiers) 174 | const {arrowType} = this.typesForColorKey(colorKey) 175 | this.drawPreviewArrow(this.dragStart.square, toSquare, arrowType) 176 | } 177 | 178 | drawPreviewArrow(from, to, type) { 179 | if(!this.previewArrowType) { 180 | this.previewArrowType = {...type} 181 | } 182 | this.chessboard.removeArrows(this.previewArrowType) 183 | this.chessboard.addArrow(this.previewArrowType, from, to) 184 | } 185 | 186 | removePreviewArrow() { 187 | if(this.previewArrowType) { 188 | this.chessboard.removeArrows(this.previewArrowType) 189 | this.previewArrowType = undefined 190 | } 191 | } 192 | 193 | modifiersToColorKey(modifiers) { 194 | if (modifiers.shift && modifiers.alt) return "warning" 195 | if (modifiers.shift) return "danger" 196 | if (modifiers.alt) return "info" 197 | return "success" 198 | } 199 | 200 | typesForColorKey(colorKey) { 201 | switch (colorKey) { 202 | case "info": 203 | return {arrowType: ARROW_TYPE.info, circleType: MARKER_TYPE.info} 204 | case "danger": 205 | return {arrowType: ARROW_TYPE.danger, circleType: MARKER_TYPE.danger} 206 | case "warning": 207 | return {arrowType: ARROW_TYPE.warning, circleType: MARKER_TYPE.warning} 208 | case "success": 209 | default: 210 | return {arrowType: ARROW_TYPE.success, circleType: MARKER_TYPE.success} 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/lib/Svg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | const SVG_NAMESPACE = "http://www.w3.org/2000/svg" 8 | 9 | export class Svg { 10 | 11 | /** 12 | * create the Svg in the HTML DOM 13 | * @param containerElement 14 | * @returns {Element} 15 | */ 16 | static createSvg(containerElement = undefined) { 17 | let svg = document.createElementNS(SVG_NAMESPACE, "svg") 18 | if (containerElement) { 19 | svg.setAttribute("width", "100%") 20 | svg.setAttribute("height", "100%") 21 | containerElement.appendChild(svg) 22 | } 23 | return svg 24 | } 25 | 26 | /** 27 | * Add an Element to an SVG DOM 28 | * @param parent 29 | * @param name 30 | * @param attributes 31 | * @returns {Element} 32 | */ 33 | static addElement(parent, name, attributes = {}) { 34 | let element = document.createElementNS(SVG_NAMESPACE, name) 35 | if (name === "use") { 36 | attributes["xlink:href"] = attributes["href"] // fix for safari 37 | } 38 | for (let attribute in attributes) { 39 | if (attributes.hasOwnProperty(attribute)) { 40 | if (attribute.indexOf(":") !== -1) { 41 | const value = attribute.split(":") 42 | element.setAttributeNS("http://www.w3.org/1999/" + value[0], value[1], attributes[attribute]) 43 | } else { 44 | element.setAttribute(attribute, attributes[attribute]) 45 | } 46 | } 47 | } 48 | parent.appendChild(element) 49 | return element 50 | } 51 | 52 | /** 53 | * Remove an element from an SVG DOM 54 | * @param element 55 | */ 56 | static removeElement(element) { 57 | if(!element) { 58 | console.warn("removeElement, element is", element) 59 | return 60 | } 61 | if (element.parentNode) { 62 | element.parentNode.removeChild(element) 63 | } else { 64 | console.warn(element, "without parentNode") 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/lib/Utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | export class Utils { 8 | 9 | static delegate(element, eventName, selector, handler) { 10 | const eventListener = function (event) { 11 | let target = event.target 12 | while (target && target !== this) { 13 | if (target.matches(selector)) { 14 | handler.call(target, event) 15 | } 16 | target = target.parentNode 17 | } 18 | } 19 | element.addEventListener(eventName, eventListener) 20 | return { 21 | remove: function () { 22 | element.removeEventListener(eventName, eventListener) 23 | } 24 | } 25 | } 26 | 27 | static mergeObjects(target, source) { 28 | const isObject = (obj) => obj && typeof obj === 'object' 29 | if (!isObject(target) || !isObject(source)) { 30 | return source 31 | } 32 | for (const key of Object.keys(source)) { 33 | if (source[key] instanceof Object) { 34 | Object.assign(source[key], Utils.mergeObjects(target[key], source[key])) 35 | } 36 | } 37 | Object.assign(target || {}, source) 38 | return target 39 | } 40 | 41 | static createDomElement(html) { 42 | const template = document.createElement('template') 43 | template.innerHTML = html.trim() 44 | return template.content.firstChild 45 | } 46 | 47 | static createTask() { 48 | let resolve, reject 49 | const promise = new Promise(function (_resolve, _reject) { 50 | resolve = _resolve 51 | reject = _reject 52 | }) 53 | promise.resolve = resolve 54 | promise.reject = reject 55 | return promise 56 | } 57 | 58 | static isAbsoluteUrl(url) { 59 | return url.indexOf("://") !== -1 || url.startsWith("/") 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/model/ChessboardState.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {Position} from "./Position.js" 7 | 8 | export class ChessboardState { 9 | 10 | constructor() { 11 | this.position = new Position() 12 | this.orientation = undefined 13 | this.inputWhiteEnabled = false 14 | this.inputBlackEnabled = false 15 | this.squareSelectEnabled = false 16 | this.moveInputCallback = null 17 | this.extensionPoints = {} 18 | this.moveInputProcess = Promise.resolve() 19 | } 20 | 21 | inputEnabled() { 22 | return this.inputWhiteEnabled || this.inputBlackEnabled 23 | } 24 | 25 | invokeExtensionPoints(name, data = {}) { 26 | const extensionPoints = this.extensionPoints[name] 27 | const dataCloned = Object.assign({}, data) 28 | dataCloned.extensionPoint = name 29 | let returnValue = true 30 | if (extensionPoints) { 31 | for (const extensionPoint of extensionPoints) { 32 | if(extensionPoint(dataCloned) === false) { 33 | returnValue = false 34 | } 35 | } 36 | } 37 | return returnValue 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/model/Extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | export const EXTENSION_POINT = { 8 | positionChanged: "positionChanged", // the positions of the pieces was changed 9 | boardChanged: "boardChanged", // the board (orientation) was changed 10 | moveInputToggled: "moveInputToggled", // move input was enabled or disabled 11 | moveInput: "moveInput", // move started, moving over a square, validating or canceled 12 | beforeRedrawBoard: "beforeRedrawBoard", // called before redrawing the board 13 | afterRedrawBoard: "afterRedrawBoard", // called after redrawing the board 14 | redrawBoard: "redrawBoard", // called after redrawing the board, DEPRECATED, use afterRedrawBoard 2023-09-18 15 | animation: "animation", // called on animation start, end, and on every animation frame 16 | destroy: "destroy" // called, before the board is destroyed 17 | } 18 | 19 | export class Extension { 20 | 21 | constructor(chessboard) { 22 | this.chessboard = chessboard 23 | } 24 | 25 | registerExtensionPoint(name, callback) { 26 | if(name === EXTENSION_POINT.redrawBoard) { // deprecated 2023-09-18 27 | console.warn("EXTENSION_POINT.redrawBoard is deprecated, use EXTENSION_POINT.afterRedrawBoard") 28 | name = EXTENSION_POINT.afterRedrawBoard 29 | } 30 | if (!this.chessboard.state.extensionPoints[name]) { 31 | this.chessboard.state.extensionPoints[name] = [] 32 | } 33 | this.chessboard.state.extensionPoints[name].push(callback) 34 | } 35 | 36 | /** @deprecated 2023-05-18 */ 37 | registerMethod(name, callback) { 38 | console.warn("registerMethod is deprecated, just add methods directly to the chessboard instance") 39 | if (!this.chessboard[name]) { 40 | this.chessboard[name] = (...args) => { 41 | return callback.apply(this, args) 42 | } 43 | } else { 44 | log.error("method", name, "already exists") 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/model/Position.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | export const FEN = { 7 | start: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 8 | empty: "8/8/8/8/8/8/8/8" 9 | } 10 | 11 | export class Position { 12 | 13 | constructor(fen = FEN.empty) { 14 | this.squares = new Array(64).fill(null) 15 | this.setFen(fen) 16 | } 17 | 18 | setFen(fen = FEN.empty) { 19 | const parts = fen.replace(/^\s*/, "").replace(/\s*$/, "").split(/\/|\s/) 20 | for (let part = 0; part < 8; part++) { 21 | const row = parts[7 - part].replace(/\d/g, (str) => { 22 | const numSpaces = parseInt(str) 23 | let ret = '' 24 | for (let i = 0; i < numSpaces; i++) { 25 | ret += '-' 26 | } 27 | return ret 28 | }) 29 | for (let c = 0; c < 8; c++) { 30 | const char = row.substring(c, c + 1) 31 | let piece = null 32 | if (char !== '-') { 33 | if (char.toUpperCase() === char) { 34 | piece = `w${char.toLowerCase()}` 35 | } else { 36 | piece = `b${char}` 37 | } 38 | } 39 | this.squares[part * 8 + c] = piece 40 | } 41 | } 42 | } 43 | 44 | getFen() { 45 | let parts = new Array(8).fill("") 46 | for (let part = 0; part < 8; part++) { 47 | let spaceCounter = 0 48 | for (let i = 0; i < 8; i++) { 49 | const piece = this.squares[part * 8 + i] 50 | if (!piece) { 51 | spaceCounter++ 52 | } else { 53 | if (spaceCounter > 0) { 54 | parts[7 - part] += spaceCounter 55 | spaceCounter = 0 56 | } 57 | const color = piece.substring(0, 1) 58 | const name = piece.substring(1, 2) 59 | if (color === "w") { 60 | parts[7 - part] += name.toUpperCase() 61 | } else { 62 | parts[7 - part] += name 63 | } 64 | } 65 | } 66 | if (spaceCounter > 0) { 67 | parts[7 - part] += spaceCounter 68 | spaceCounter = 0 69 | } 70 | } 71 | return parts.join("/") 72 | } 73 | 74 | getPieces(pieceColor = undefined, pieceType = undefined, sortBy = ['k', 'q', 'r', 'b', 'n', 'p']) { 75 | const pieces = [] 76 | const sort = (a, b) => { 77 | return sortBy.indexOf(a.name) - sortBy.indexOf(b.name) 78 | } 79 | for (let i = 0; i < 64; i++) { 80 | const piece = this.squares[i] 81 | if (piece) { 82 | const type = piece.charAt(1) 83 | const color = piece.charAt(0) 84 | const square = Position.indexToSquare(i) 85 | if(pieceType && pieceType !== type || pieceColor && pieceColor !== color) { 86 | continue 87 | } 88 | pieces.push({ 89 | name: type, // deprecated, use type 90 | type: type, 91 | color: color, 92 | position: square, // deprecated, use square 93 | square: square 94 | }) 95 | } 96 | } 97 | if (sortBy) { 98 | pieces.sort(sort) 99 | } 100 | return pieces 101 | } 102 | 103 | movePiece(squareFrom, squareTo) { 104 | if (!this.squares[Position.squareToIndex(squareFrom)]) { 105 | console.warn("no piece on", squareFrom) 106 | return 107 | } 108 | this.squares[Position.squareToIndex(squareTo)] = this.squares[Position.squareToIndex(squareFrom)] 109 | this.squares[Position.squareToIndex(squareFrom)] = null 110 | } 111 | 112 | setPiece(square, piece) { 113 | this.squares[Position.squareToIndex(square)] = piece 114 | } 115 | 116 | getPiece(square) { 117 | return this.squares[Position.squareToIndex(square)] 118 | } 119 | 120 | static squareToIndex(square) { 121 | const coordinates = Position.squareToCoordinates(square) 122 | return coordinates[0] + coordinates[1] * 8 123 | } 124 | 125 | static indexToSquare(index) { 126 | return this.coordinatesToSquare([Math.floor(index % 8), index / 8]) 127 | } 128 | 129 | static squareToCoordinates(square) { 130 | const file = square.charCodeAt(0) - 97 131 | const rank = square.charCodeAt(1) - 49 132 | return [file, rank] 133 | } 134 | 135 | static coordinatesToSquare(coordinates) { 136 | const file = String.fromCharCode(coordinates[0] + 97) 137 | const rank = String.fromCharCode(coordinates[1] + 49) 138 | return file + rank 139 | } 140 | 141 | toString() { 142 | return this.getFen() 143 | } 144 | 145 | clone() { 146 | const cloned = new Position() 147 | cloned.squares = this.squares.slice(0) 148 | return cloned 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/view/PositionAnimationsQueue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | import {FEN, Position} from "../model/Position.js" 7 | import {Svg} from "../lib/Svg.js" 8 | import {EXTENSION_POINT} from "../model/Extension.js" 9 | import {Utils} from "../lib/Utils.js" 10 | 11 | /* 12 | * Thanks to markosyan for the idea of the PromiseQueue 13 | * https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5 14 | */ 15 | 16 | export const ANIMATION_EVENT_TYPE = { 17 | start: "start", 18 | frame: "frame", 19 | end: "end" 20 | } 21 | 22 | export class PromiseQueue { 23 | 24 | constructor() { 25 | this.queue = [] 26 | this.workingOnPromise = false 27 | this.stop = false 28 | } 29 | 30 | async enqueue(promise) { 31 | return new Promise((resolve, reject) => { 32 | this.queue.push({ 33 | promise, resolve, reject, 34 | }) 35 | this.dequeue() 36 | }) 37 | } 38 | 39 | dequeue() { 40 | if (this.workingOnPromise) { 41 | return 42 | } 43 | if (this.stop) { 44 | this.queue = [] 45 | this.stop = false 46 | return 47 | } 48 | const entry = this.queue.shift() 49 | if (!entry) { 50 | return 51 | } 52 | try { 53 | this.workingOnPromise = true 54 | entry.promise().then((value) => { 55 | this.workingOnPromise = false 56 | entry.resolve(value) 57 | this.dequeue() 58 | }).catch(err => { 59 | this.workingOnPromise = false 60 | entry.reject(err) 61 | this.dequeue() 62 | }) 63 | } catch (err) { 64 | this.workingOnPromise = false 65 | entry.reject(err) 66 | this.dequeue() 67 | } 68 | return true 69 | } 70 | 71 | destroy() { 72 | this.stop = true 73 | } 74 | 75 | } 76 | 77 | 78 | const CHANGE_TYPE = { 79 | move: 0, 80 | appear: 1, 81 | disappear: 2 82 | } 83 | 84 | export class PositionsAnimation { 85 | 86 | constructor(view, fromPosition, toPosition, duration, callback) { 87 | this.view = view 88 | if (fromPosition && toPosition) { 89 | this.animatedElements = this.createAnimation(fromPosition.squares, toPosition.squares) 90 | this.duration = duration 91 | this.callback = callback 92 | this.frameHandle = requestAnimationFrame(this.animationStep.bind(this)) 93 | } else { 94 | console.error("fromPosition", fromPosition, "toPosition", toPosition) 95 | } 96 | this.view.positionsAnimationTask = Utils.createTask() 97 | this.view.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.animation, { 98 | type: ANIMATION_EVENT_TYPE.start 99 | }) 100 | } 101 | 102 | static seekChanges(fromSquares, toSquares) { 103 | const appearedList = [], disappearedList = [], changes = [] 104 | for (let i = 0; i < 64; i++) { 105 | const previousSquare = fromSquares[i] 106 | const newSquare = toSquares[i] 107 | if (newSquare !== previousSquare) { 108 | if (newSquare) { 109 | appearedList.push({piece: newSquare, index: i}) 110 | } 111 | if (previousSquare) { 112 | disappearedList.push({piece: previousSquare, index: i}) 113 | } 114 | } 115 | } 116 | appearedList.forEach((appeared) => { 117 | let shortestDistance = 8 118 | let foundMoved = null 119 | disappearedList.forEach((disappeared) => { 120 | if (appeared.piece === disappeared.piece) { 121 | const moveDistance = PositionsAnimation.squareDistance(appeared.index, disappeared.index) 122 | if (moveDistance < shortestDistance) { 123 | foundMoved = disappeared 124 | shortestDistance = moveDistance 125 | } 126 | } 127 | }) 128 | if (foundMoved) { 129 | disappearedList.splice(disappearedList.indexOf(foundMoved), 1) // remove from disappearedList, because it is moved now 130 | changes.push({ 131 | type: CHANGE_TYPE.move, 132 | piece: appeared.piece, 133 | atIndex: foundMoved.index, 134 | toIndex: appeared.index 135 | }) 136 | } else { 137 | changes.push({type: CHANGE_TYPE.appear, piece: appeared.piece, atIndex: appeared.index}) 138 | } 139 | }) 140 | disappearedList.forEach((disappeared) => { 141 | changes.push({type: CHANGE_TYPE.disappear, piece: disappeared.piece, atIndex: disappeared.index}) 142 | }) 143 | return changes 144 | } 145 | 146 | createAnimation(fromSquares, toSquares) { 147 | const changes = PositionsAnimation.seekChanges(fromSquares, toSquares) 148 | const animatedElements = [] 149 | changes.forEach((change) => { 150 | const animatedItem = { 151 | type: change.type 152 | } 153 | switch (change.type) { 154 | case CHANGE_TYPE.move: 155 | animatedItem.element = this.view.getPieceElement(Position.indexToSquare(change.atIndex)) 156 | animatedItem.element.parentNode.appendChild(animatedItem.element) // move element to top layer 157 | animatedItem.atPoint = this.view.indexToPoint(change.atIndex) 158 | animatedItem.toPoint = this.view.indexToPoint(change.toIndex) 159 | break 160 | case CHANGE_TYPE.appear: 161 | animatedItem.element = this.view.drawPieceOnSquare(Position.indexToSquare(change.atIndex), change.piece) 162 | animatedItem.element.style.opacity = 0 163 | break 164 | case CHANGE_TYPE.disappear: 165 | animatedItem.element = this.view.getPieceElement(Position.indexToSquare(change.atIndex)) 166 | break 167 | } 168 | animatedElements.push(animatedItem) 169 | }) 170 | return animatedElements 171 | } 172 | 173 | animationStep(time) { 174 | if(!this.view || !this.view.chessboard.state) { // board was destroyed 175 | return 176 | } 177 | if (!this.startTime) { 178 | this.startTime = time 179 | } 180 | const timeDiff = time - this.startTime 181 | if (timeDiff <= this.duration) { 182 | this.frameHandle = requestAnimationFrame(this.animationStep.bind(this)) 183 | } else { 184 | cancelAnimationFrame(this.frameHandle) 185 | this.animatedElements.forEach((animatedItem) => { 186 | if (animatedItem.type === CHANGE_TYPE.disappear) { 187 | Svg.removeElement(animatedItem.element) 188 | } 189 | }) 190 | this.view.positionsAnimationTask.resolve() 191 | this.view.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.animation, { 192 | type: ANIMATION_EVENT_TYPE.end 193 | }) 194 | this.callback() 195 | return 196 | } 197 | const t = Math.min(1, timeDiff / this.duration) 198 | let progress = t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t // easeInOut 199 | if (isNaN(progress) || progress > 0.99) { 200 | progress = 1 201 | } 202 | this.animatedElements.forEach((animatedItem) => { 203 | if (animatedItem.element) { 204 | switch (animatedItem.type) { 205 | case CHANGE_TYPE.move: 206 | animatedItem.element.transform.baseVal.removeItem(0) 207 | const transform = (this.view.svg.createSVGTransform()) 208 | transform.setTranslate( 209 | animatedItem.atPoint.x + (animatedItem.toPoint.x - animatedItem.atPoint.x) * progress, 210 | animatedItem.atPoint.y + (animatedItem.toPoint.y - animatedItem.atPoint.y) * progress) 211 | animatedItem.element.transform.baseVal.appendItem(transform) 212 | break 213 | case CHANGE_TYPE.appear: 214 | animatedItem.element.style.opacity = Math.round(progress * 100) / 100 215 | break 216 | case CHANGE_TYPE.disappear: 217 | animatedItem.element.style.opacity = Math.round((1 - progress) * 100) / 100 218 | break 219 | } 220 | } else { 221 | console.warn("animatedItem has no element", animatedItem) 222 | } 223 | }) 224 | this.view.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.animation, { 225 | type: ANIMATION_EVENT_TYPE.frame, 226 | progress: progress 227 | }) 228 | } 229 | 230 | static squareDistance(index1, index2) { 231 | const file1 = index1 % 8 232 | const rank1 = Math.floor(index1 / 8) 233 | const file2 = index2 % 8 234 | const rank2 = Math.floor(index2 / 8) 235 | return Math.max(Math.abs(rank2 - rank1), Math.abs(file2 - file1)) 236 | } 237 | 238 | } 239 | 240 | export class PositionAnimationsQueue extends PromiseQueue { 241 | 242 | constructor(chessboard) { 243 | super() 244 | this.chessboard = chessboard 245 | } 246 | 247 | async enqueuePositionChange(positionFrom, positionTo, animated) { 248 | if(positionFrom.getFen() === positionTo.getFen()) { 249 | return Promise.resolve() 250 | } else { 251 | return super.enqueue(() => new Promise((resolve) => { 252 | let duration = animated ? this.chessboard.props.style.animationDuration : 0 253 | if (this.queue.length > 0) { 254 | duration = duration / (1 + Math.pow(this.queue.length / 5, 2)) 255 | } 256 | new PositionsAnimation(this.chessboard.view, 257 | positionFrom, positionTo, animated ? duration : 0, 258 | () => { 259 | if (this.chessboard.view) { // if destroyed, no view anymore 260 | this.chessboard.view.redrawPieces(positionTo.squares) 261 | } 262 | resolve() 263 | } 264 | ) 265 | })) 266 | } 267 | } 268 | 269 | async enqueueTurnBoard(position, color, animated) { 270 | return super.enqueue(() => new Promise((resolve) => { 271 | const emptyPosition = new Position(FEN.empty) 272 | let duration = animated ? this.chessboard.props.style.animationDuration : 0 273 | if(this.queue.length > 0) { 274 | duration = duration / (1 + Math.pow(this.queue.length / 5, 2)) 275 | } 276 | new PositionsAnimation(this.chessboard.view, 277 | position, emptyPosition, animated ? duration : 0, 278 | () => { 279 | this.chessboard.state.orientation = color 280 | this.chessboard.view.redrawBoard() 281 | this.chessboard.view.redrawPieces(emptyPosition.squares) 282 | new PositionsAnimation(this.chessboard.view, 283 | emptyPosition, position, animated ? duration : 0, 284 | () => { 285 | this.chessboard.view.redrawPieces(position.squares) 286 | resolve() 287 | } 288 | ) 289 | } 290 | ) 291 | })) 292 | } 293 | 294 | } 295 | -------------------------------------------------------------------------------- /test/TestChessboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {describe, it, assert} from "../node_modules/teevi/src/teevi.js" 8 | import {PIECE, Chessboard} from "../src/Chessboard.js" 9 | import {FEN} from "../src/model/Position.js" 10 | 11 | describe("TestChessboard", () => { 12 | 13 | // https://github.com/shaack/cm-chessboard/issues/47 14 | it("should create and immediately destroy a board without failure", () => { 15 | const chessboard = new Chessboard(document.getElementById("TestBoard"), { 16 | assetsUrl: "../assets/", 17 | position: FEN.start 18 | }) 19 | chessboard.destroy() 20 | }) 21 | 22 | it("should create and destroy a board", () => { 23 | const chessboard = new Chessboard(document.getElementById("TestBoard"), { 24 | assetsUrl: "../assets/", 25 | position: FEN.start 26 | }) 27 | assert.equal(chessboard.view.container.childNodes.length, 1) 28 | chessboard.destroy() 29 | assert.equal(chessboard.state, undefined) 30 | }) 31 | 32 | it("should create and destroy a chessboard", () => { 33 | const chessboard = new Chessboard(document.getElementById("TestPosition"), { 34 | assetsUrl: "../assets/", 35 | position: FEN.start 36 | }) 37 | assert.equal("" + chessboard.getPosition(), "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR") 38 | chessboard.destroy() 39 | }) 40 | 41 | it("should set and get the position", () => { 42 | const chessboard = new Chessboard(document.getElementById("TestPosition"), 43 | {assetsUrl: "../assets/"}) 44 | chessboard.setPosition("rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR w Gkq - 4 11", false).then(() => { 45 | assert.equal("" + chessboard.getPosition(), "rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR") 46 | chessboard.destroy() 47 | }) 48 | }) 49 | 50 | it("should get pieces on squares", () => { 51 | const chessboard = new Chessboard(document.getElementById("TestPosition"), { 52 | assetsUrl: "../assets/", 53 | position: FEN.start 54 | }) 55 | assert.equal(chessboard.getPiece("d1"), "wq") 56 | assert.equal(chessboard.getPiece("d8"), "bq") 57 | assert.equal(chessboard.getPiece("a2"), "wp") 58 | chessboard.destroy() 59 | }) 60 | 61 | it("should set pieces on squares", () => { 62 | const chessboard = new Chessboard(document.getElementById("TestPosition"), { 63 | assetsUrl: "../assets/", 64 | }) 65 | chessboard.setPiece("a1", PIECE.bk) 66 | assert.equal(chessboard.getPiece("a1"), "bk") 67 | chessboard.setPiece("e5", PIECE.wk) 68 | assert.equal(chessboard.getPiece("e5"), "wk") 69 | chessboard.destroy() 70 | }) 71 | 72 | }) 73 | -------------------------------------------------------------------------------- /test/TestMarkers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {describe, it, assert} from "../node_modules/teevi/src/teevi.js" 8 | import {Chessboard} from "../src/Chessboard.js" 9 | import {MARKER_TYPE, Markers} from "../src/extensions/markers/Markers.js" 10 | 11 | describe("TestMarkers", () => { 12 | 13 | it("should set and get markers", () => { 14 | const chessboard = new Chessboard(document.getElementById("TestMarkers"), { 15 | assetsUrl: "../assets/", 16 | extensions: [{class: Markers}] 17 | }) 18 | chessboard.addMarker(MARKER_TYPE.square, "e5") 19 | chessboard.addMarker(MARKER_TYPE.frame, "b6") 20 | chessboard.addMarker(MARKER_TYPE.frame, "h6") 21 | assert.equal(chessboard.getMarkers().length, 3) 22 | const markersE5 = chessboard.getMarkers(undefined,"e5") 23 | assert.equal(markersE5.length, 1) 24 | assert.equal(markersE5[0].square, "e5") 25 | assert.equal(markersE5[0].type.slice, "markerSquare") 26 | assert.equal(chessboard.getMarkers(MARKER_TYPE.square).length, 1) 27 | assert.equal(chessboard.getMarkers(undefined, "a4").length, 0) 28 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame).length, 2) 29 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "b6").length, 1) 30 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "h6").length, 1) 31 | chessboard.removeMarkers(undefined, "h6") 32 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "h6").length, 0) 33 | chessboard.addMarker(MARKER_TYPE.frame, "h6") 34 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "h6").length, 1) 35 | chessboard.removeMarkers(MARKER_TYPE.square, "h6") 36 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "h6").length, 1) 37 | chessboard.removeMarkers(MARKER_TYPE.frame, "h6") 38 | assert.equal(chessboard.getMarkers(MARKER_TYPE.frame, "h6").length, 0) 39 | chessboard.destroy() 40 | }) 41 | 42 | }) 43 | -------------------------------------------------------------------------------- /test/TestPiecesAnimation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {describe, it, assert} from "../node_modules/teevi/src/teevi.js" 8 | import {ChessboardState} from "../src/model/ChessboardState.js" 9 | import {PositionsAnimation} from "../src/view/PositionAnimationsQueue.js" 10 | import {Position} from "../src/model/Position.js" 11 | 12 | describe("TestPiecesAnimation", () => { 13 | it("should calculate square distances", () => { 14 | assert.equal(PositionsAnimation.squareDistance(0, 0), 0) 15 | assert.equal(PositionsAnimation.squareDistance(0, 1), 1) 16 | assert.equal(PositionsAnimation.squareDistance(0, 7), 7) 17 | assert.equal(PositionsAnimation.squareDistance(0, 8), 1) 18 | assert.equal(PositionsAnimation.squareDistance(10, 20), 2) 19 | assert.equal(PositionsAnimation.squareDistance(0, 63), 7) 20 | assert.equal(PositionsAnimation.squareDistance(8, 24), 2) 21 | assert.equal(PositionsAnimation.squareDistance(14, 24), 6) 22 | }) 23 | 24 | it("should seek changes", () => { 25 | const state1 = new ChessboardState() 26 | state1.position = new Position("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR") 27 | const state2 = new ChessboardState() 28 | state2.position = new Position("rn2k1r1/ppp1pp1p/3p2p1/5bn1/P7/2N2B2/1PPPPP2/2BNK1RR") 29 | const previousBoard1 = state1.position.squares 30 | const newBoard1 = state2.position.squares 31 | const changes = PositionsAnimation.seekChanges(previousBoard1, newBoard1) 32 | 33 | assert.equal(changes[0].type,0 ) 34 | assert.equal(changes[0].piece, "wn") 35 | assert.equal(changes[0].atIndex, 1) 36 | assert.equal(changes[0].toIndex,3) 37 | 38 | assert.equal(changes[2].type, 0) 39 | assert.equal(changes[2].piece, "wn") 40 | assert.equal(changes[2].atIndex, 6) 41 | assert.equal(changes[2].toIndex, 18) 42 | 43 | assert.equal(changes[4].type,0 ) 44 | assert.equal(changes[4].piece, "wp") 45 | assert.equal(changes[4].atIndex, 8) 46 | assert.equal(changes[4].toIndex, 24) 47 | 48 | assert.equal(changes[13].type, 2) 49 | assert.equal(changes[13].piece, "bq") 50 | assert.equal(changes[13].atIndex, 59) 51 | }) 52 | 53 | }) 54 | -------------------------------------------------------------------------------- /test/TestPosition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/cm-chessboard 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | import {describe, it, assert} from "../node_modules/teevi/src/teevi.js" 8 | import {FEN, Position} from "../src/model/Position.js" 9 | import {COLOR, PIECE_TYPE} from "../src/Chessboard.js" 10 | 11 | describe("TestPosition", () => { 12 | it("should convert square to index", () => { 13 | assert.equal(Position.squareToIndex("a1"), 0) 14 | assert.equal(Position.squareToIndex("h1"), 7) 15 | assert.equal(Position.squareToIndex("a8"), 56) 16 | assert.equal(Position.squareToIndex("g5"), 38) 17 | assert.equal(Position.squareToIndex("h8"), 63) 18 | }) 19 | it("should convert index to square", () => { 20 | assert.equal(Position.indexToSquare(0), "a1") 21 | assert.equal(Position.indexToSquare(7), "h1") 22 | assert.equal(Position.indexToSquare(56), "a8") 23 | assert.equal(Position.indexToSquare(38), "g5") 24 | assert.equal(Position.indexToSquare(63), "h8") 25 | }) 26 | it("should return the correct FEN string", () => { 27 | const position = new Position(FEN.start) 28 | assert.equal(position.getFen(), "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR") 29 | }) 30 | it("should find the correct pieces", () => { 31 | const position = new Position("8/5P2/8/1P3r2/8/8/1P3P1P/8") 32 | const blackPiece = position.getPieces(COLOR.black) 33 | assert.equal(blackPiece.length, 1) 34 | assert.equal(blackPiece[0].type, PIECE_TYPE.rook) 35 | assert.equal(blackPiece[0].square, "f5") 36 | const whitePieces = position.getPieces(COLOR.white) 37 | assert.equal(whitePieces.length, 5) 38 | const rooks = position.getPieces(undefined, PIECE_TYPE.rook) 39 | assert.equal(rooks.length, 1) 40 | assert.equal(rooks[0].square, "f5") 41 | }) 42 | }) 43 | 44 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test cm-chessboard 6 | 7 | 8 | 16 | 17 | 18 |

    cm-chessboard

    19 |

    Lightweight unit testing with Teevi.

    20 |
    21 |
    22 |
    23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/mocks/ViewMock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: shaack 3 | * Date: 10.12.2017 4 | */ 5 | 6 | export class ViewMock { 7 | constructor() { 8 | this.squareHeight = 40; 9 | this.props = { 10 | sprite: { 11 | size: 40 12 | } 13 | } 14 | } 15 | } --------------------------------------------------------------------------------