cm-chessboard 4.1.10 → 4.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,12 +101,6 @@ let defaultProps = {
101
101
  url: "./assets/images/chessboard-sprite.svg", // pieces and markers are stored in a sprite file
102
102
  size: 40, // the sprite tiles size, defaults to 40x40px
103
103
  cache: true // cache the sprite
104
- },
105
- accessibility: {
106
- movePieceForm: false, // display a form to move a piece (from, to, move)
107
- boardAsTable: false, // display the board additionally as HTML table
108
- piecesAsList: false, // display the pieces additionally as List
109
- visuallyHidden: true // hide all those extra outputs visually but keep them accessible for screen readers and braille displays
110
104
  }
111
105
  }
112
106
  ```
@@ -334,6 +328,10 @@ chessboard.removeMarkers("e4", myMarkerType)
334
328
  chessboard.removeMarkers(undefined, myMarkerType)
335
329
  ```
336
330
 
331
+ ## Extensions
332
+
333
+ cm-chessboard provides the ability to extend its functionality with extensions.
334
+
337
335
  ## Usage with React
338
336
 
339
337
  There exists a ticket from someone who is using cm-chessboard with react:
@@ -18,41 +18,38 @@
18
18
 
19
19
  <div class="board green" id="board"></div>
20
20
  <button style="margin-bottom: 10px"
21
- onclick="window.board.setOrientation(window.board.getOrientation() === 'w' ? 'b' : 'w')">Switch Orientation
21
+ onclick="window.setOrientation()">Switch Orientation
22
22
  </button>
23
23
  <script type="module">
24
24
  import {Chessboard} from "../src/cm-chessboard/Chessboard.js"
25
+ import {Accessibility} from "../src/cm-chessboard/extensions/Accessibility.js"
25
26
 
26
- window.board = new Chessboard(document.getElementById("board"), {
27
+ const chessboard = new Chessboard(document.getElementById("board"), {
27
28
  position: "start",
28
29
  sprite: {url: "../assets/images/chessboard-sprite.svg"},
29
30
  style: {
30
31
  cssClass: "default-contrast"
31
- },
32
- accessibility: {
33
- brailleNotationInAlt: true, // show the braille notation of the game in the alt attribute of the svg
34
- boardAsTable: true, // display the board additionally as HTML table
35
- movePieceForm: true, // display a form to move a piece (from, to, move)
36
- piecesAsList: true, // display the pieces additionally as List
37
- enableAnimations: true, // set to false to disable all animations
38
- visuallyHidden: false // hide all those extra outputs visually but keep them accessible for screen readers and braille displays
39
32
  }
40
33
  })
34
+ window.setOrientation = function () {
35
+ chessboard.setOrientation(chessboard.getOrientation() === 'w' ? 'b' : 'w')
36
+ }
37
+ // Extension
38
+ new Accessibility(chessboard, {
39
+ brailleNotationInAlt: true, // show the braille notation of the game in the alt attribute of the SVG
40
+ boardAsTable: true, // display the board additionally as HTML table
41
+ movePieceForm: true, // display a form to move a piece (from, to, move)
42
+ piecesAsList: true, // display the pieces additionally as List
43
+ visuallyHidden: false // hide all those extra outputs visually but keep them accessible for screen readers and braille displays
44
+ })
41
45
 
42
- window.board.enableMoveInput(inputHandler)
46
+ chessboard.enableMoveInput(inputHandler)
43
47
 
44
48
  function inputHandler(event) {
45
49
  console.log(event)
46
50
  return true
47
51
  }
48
52
 
49
- const output = document.getElementById("output")
50
-
51
- function log(text) {
52
- const log = document.createElement("div")
53
- log.innerText = text
54
- output.appendChild(log)
55
- }
56
53
  </script>
57
54
  </body>
58
55
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cm-chessboard",
3
- "version": "4.1.10",
3
+ "version": "4.1.13",
4
4
  "description": "A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.",
5
5
  "keywords": [
6
6
  "chess",
@@ -27,6 +27,6 @@
27
27
  },
28
28
  "homepage": "https://github.com/shaack/cm-chessboard#readme",
29
29
  "devDependencies": {
30
- "teevi": "^2.1.9"
30
+ "teevi": "^2.2.2"
31
31
  }
32
32
  }
@@ -6,8 +6,9 @@
6
6
 
7
7
  import {ChessboardState} from "./model/ChessboardState.js"
8
8
  import {Position} from "./model/Position.js"
9
- import {ChessboardViewAccessible} from "./view/ChessboardViewAccessible.js"
10
9
  import {PositionAnimationsQueue} from "./view/PositionAnimationsQueue.js"
10
+ import {EXTENSION_POINT} from "./model/Extension.js"
11
+ import {ChessboardView} from "./view/ChessboardView.js"
11
12
 
12
13
  export const COLOR = {
13
14
  white: "w",
@@ -46,6 +47,7 @@ export class Chessboard {
46
47
  }
47
48
  this.context = context
48
49
  this.id = (Math.random() + 1).toString(36).substring(2, 8)
50
+ this.extensions = []
49
51
  let defaultProps = {
50
52
  position: "empty", // set as fen, "start" or "empty"
51
53
  orientation: COLOR.white, // white on bottom
@@ -92,10 +94,11 @@ export class Chessboard {
92
94
  }
93
95
 
94
96
  this.state = new ChessboardState()
95
- this.view = new ChessboardViewAccessible(this)
97
+ this.view = new ChessboardView(this)
98
+ // callback Extension
96
99
  this.positionAnimationsQueue = new PositionAnimationsQueue(this)
97
100
  this.state.orientation = this.props.orientation
98
- this.view.redraw()
101
+ this.view.redrawBoard()
99
102
  this.state.position = new Position(this.props.position)
100
103
  this.view.redrawPieces()
101
104
  }
@@ -105,18 +108,21 @@ export class Chessboard {
105
108
  async setPiece(square, piece, animated = false) {
106
109
  const positionFrom = this.state.position.clone()
107
110
  this.state.position.setPiece(square, piece)
111
+ this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged)
108
112
  return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated)
109
113
  }
110
114
 
111
115
  async movePiece(squareFrom, squareTo, animated = false) {
112
116
  const positionFrom = this.state.position.clone()
113
117
  this.state.position.movePiece(squareFrom, squareTo)
118
+ this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged)
114
119
  return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated)
115
120
  }
116
121
 
117
122
  async setPosition(fen, animated = false) {
118
123
  const positionFrom = this.state.position.clone()
119
124
  this.state.position.setFen(fen)
125
+ this.state.invokeExtensionPoints(EXTENSION_POINT.positionChanged)
120
126
  return this.positionAnimationsQueue.enqueuePositionChange(positionFrom, this.state.position.clone(), animated)
121
127
  }
122
128
 
@@ -127,6 +133,7 @@ export class Chessboard {
127
133
  return
128
134
  }
129
135
  this.boardTurning = true
136
+ this.state.invokeExtensionPoints(EXTENSION_POINT.boardChanged)
130
137
  return this.positionAnimationsQueue.enqueueTurnBoard(position, color, animated).then(() => {
131
138
  this.boardTurning = false
132
139
  })
@@ -213,6 +220,7 @@ export class Chessboard {
213
220
  }
214
221
 
215
222
  destroy() {
223
+ this.state.invokeExtensionPoints(EXTENSION_POINT.destroy)
216
224
  this.positionAnimationsQueue.destroy()
217
225
  this.view.destroy()
218
226
  this.view = undefined
@@ -222,7 +230,6 @@ export class Chessboard {
222
230
  this.context.removeEventListener("mouseup", this.squareSelectListener)
223
231
  this.context.removeEventListener("touchend", this.squareSelectListener)
224
232
  }
225
- this.destroyed = true
226
233
  }
227
234
 
228
235
  }
@@ -3,9 +3,9 @@
3
3
  * Repository: https://github.com/shaack/cm-chessboard
4
4
  * License: MIT, see file 'LICENSE'
5
5
  */
6
- import {ChessboardView, renderPieceTitle} from "./ChessboardView.js"
6
+ import {Extension, EXTENSION_POINT} from "../model/Extension.js"
7
+ import {piecesTranslations, renderPieceTitle} from "../view/ChessboardView.js"
7
8
  import {COLOR, INPUT_EVENT_TYPE} from "../Chessboard.js"
8
- import {piecesTranslations} from "./ChessboardView.js"
9
9
 
10
10
  const hlTranslations = {
11
11
  de: {
@@ -32,17 +32,23 @@ const hlTranslations = {
32
32
  }
33
33
  }
34
34
 
35
- export class ChessboardViewAccessible extends ChessboardView {
35
+ export class Accessibility extends Extension {
36
36
 
37
- constructor(chessboard, callbackAfterCreation) {
38
- super(chessboard, callbackAfterCreation)
39
- this.lang = this.chessboard.props.language
37
+ constructor(chessboard, props) {
38
+ super(chessboard, props)
39
+ this.props = {
40
+ brailleNotationInAlt: true, // show the braille notation of the game in the alt attribute of the SVG
41
+ boardAsTable: false, // display the board additionally as HTML table
42
+ movePieceForm: false, // display a form to move a piece (from, to, move)
43
+ piecesAsList: false, // display the pieces additionally as List
44
+ visuallyHidden: true // hide all those extra outputs visually but keep them accessible for screen readers and braille displays
45
+ }
46
+ Object.assign(this.props, props)
47
+ this.lang = chessboard.props.language
40
48
  this.translations = piecesTranslations
41
49
  this.t = this.translations[this.lang]
42
50
  this.th = hlTranslations[this.lang]
43
-
44
- const accessibilityProps = chessboard.props.accessibility
45
- if (accessibilityProps.movePieceForm) {
51
+ if (this.props.movePieceForm) {
46
52
  this.movePieceFormContainer = this.createElement(`<div><h3>${this.th.move_piece}</h3><form>
47
53
  <label for="move_piece_input_from_${chessboard.id}">${this.th.from}</label><input class="input-from" type="text" size="2" id="move_piece_input_from_${chessboard.id}"/>
48
54
  <label for="move_piece_input_to_${chessboard.id}">${this.th.to}</label><input class="input-to" type="text" size="2" id="move_piece_input_to_${chessboard.id}"/>
@@ -52,12 +58,12 @@ export class ChessboardViewAccessible extends ChessboardView {
52
58
  this.inputFrom = this.form.querySelector(".input-from")
53
59
  this.inputTo = this.form.querySelector(".input-to")
54
60
  this.moveButton = this.form.querySelector(".button-move")
55
- if (accessibilityProps.visuallyHidden) {
61
+ if (this.props.visuallyHidden) {
56
62
  this.movePieceFormContainer.classList.add("visually-hidden")
57
63
  }
58
64
  this.form.addEventListener("submit", (evt) => {
59
65
  evt.preventDefault()
60
- if (this.moveInputCallback({
66
+ if (this.chessboard.view.moveInputCallback({
61
67
  chessboard: this.chessboard,
62
68
  type: INPUT_EVENT_TYPE.moveDone,
63
69
  squareFrom: this.inputFrom.value,
@@ -72,28 +78,47 @@ export class ChessboardViewAccessible extends ChessboardView {
72
78
  })
73
79
  this.chessboard.context.appendChild(this.movePieceFormContainer)
74
80
  }
75
- if (accessibilityProps.boardAsTable) {
81
+ if (this.props.boardAsTable) {
76
82
  this.boardAsTableContainer = this.createElement(`<div><h3>${this.th.board_as_table}</h3><div class="table"></div></div>`)
77
83
  this.boardAsTable = this.boardAsTableContainer.querySelector(".table")
78
84
  this.chessboard.context.appendChild(this.boardAsTableContainer)
79
- if (accessibilityProps.visuallyHidden) {
85
+ if (this.props.visuallyHidden) {
80
86
  this.boardAsTableContainer.classList.add("visually-hidden")
81
87
  }
82
88
  }
83
- if (accessibilityProps.piecesAsList) {
89
+ if (this.props.piecesAsList) {
84
90
  this.piecesListContainer = this.createElement(`<div><h3>${this.th.pieces_lists}</h3><div class="list"></div></div>`)
85
91
  this.piecesList = this.piecesListContainer.querySelector(".list")
86
92
  this.chessboard.context.appendChild(this.piecesListContainer)
87
- if (accessibilityProps.visuallyHidden) {
93
+ if (this.props.visuallyHidden) {
88
94
  this.piecesListContainer.classList.add("visually-hidden")
89
95
  }
90
96
  }
91
- this.updateFormInputs()
92
- }
97
+ this.registerExtensionPoint(EXTENSION_POINT.moveInputStateChanged, () => {
98
+ this.updateFormInputs()
99
+ })
100
+ this.registerExtensionPoint(EXTENSION_POINT.positionChanged, () => {
101
+ if(!this.chessboard.state.destroyed) {
102
+ this.redrawPositionInAltAttribute()
103
+ if (this.props.boardAsTable) {
104
+ this.redrawBoardAsTable()
105
+ }
106
+ if (this.props.piecesAsList) {
107
+ this.redrawPiecesLists()
108
+ }
109
+ }
110
+ })
111
+ setTimeout(() => {
112
+ this.updateFormInputs()
113
+ this.redrawPositionInAltAttribute()
114
+ if (this.props.boardAsTable) {
115
+ this.redrawBoardAsTable()
116
+ }
117
+ if (this.props.piecesAsList) {
118
+ this.redrawPiecesLists()
119
+ }
93
120
 
94
- visualizeInputState() {
95
- super.visualizeInputState()
96
- this.updateFormInputs()
121
+ })
97
122
  }
98
123
 
99
124
  updateFormInputs() {
@@ -110,21 +135,6 @@ export class ChessboardViewAccessible extends ChessboardView {
110
135
  }
111
136
  }
112
137
 
113
- redrawPieces(squares = this.chessboard.state.position.squares) {
114
- super.redrawPieces(squares)
115
- setTimeout(() => {
116
- if(!this.chessboard.destroyed) {
117
- this.redrawPositionInAltAttribute()
118
- if (this.chessboard.props.accessibility.boardAsTable) {
119
- this.redrawBoardAsTable()
120
- }
121
- if (this.chessboard.props.accessibility.piecesAsList) {
122
- this.redrawPiecesLists()
123
- }
124
- }
125
- })
126
- }
127
-
128
138
  redrawPositionInAltAttribute() {
129
139
  const pieces = this.chessboard.state.position.getPieces()
130
140
  let listW = piecesTranslations[this.lang].colors.w.toUpperCase() + ":"
@@ -169,7 +179,7 @@ ${listB}`
169
179
  files.reverse()
170
180
  squares.reverse()
171
181
  }
172
- let html = `<table><caption>${this.th.board_as_table}</caption><tr><th></th>`
182
+ let html = `<table><!--<caption>${this.th.board_as_table}</caption>--><tr><th></th>`
173
183
  for (const rank of ranks) {
174
184
  html += `<th scope='col'>${rank}</th>`
175
185
  }
@@ -15,6 +15,7 @@ export class ChessboardState {
15
15
  this.inputBlackEnabled = false
16
16
  this.inputEnabled = false
17
17
  this.squareSelectEnabled = false
18
+ this.extensionPoints = {}
18
19
  }
19
20
 
20
21
  setPosition(fen, animated = false) {
@@ -65,4 +66,15 @@ export class ChessboardState {
65
66
  }
66
67
  }
67
68
 
69
+ invokeExtensionPoints(name, data = undefined) {
70
+ const extensionPoints = this.extensionPoints[name]
71
+ if(extensionPoints) {
72
+ for (const extensionPoint of extensionPoints) {
73
+ setTimeout(() => {
74
+ extensionPoint(data)
75
+ })
76
+ }
77
+ }
78
+ }
79
+
68
80
  }
@@ -0,0 +1,28 @@
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",
9
+ boardChanged: "boardChanged",
10
+ moveInputStateChanged: "moveInputStateChanged",
11
+ destroy: "destroy"
12
+ }
13
+
14
+ export class Extension {
15
+
16
+ constructor(chessboard, props) {
17
+ this.chessboard = chessboard
18
+ this.props = props
19
+ }
20
+
21
+ registerExtensionPoint(name, callback) {
22
+ if(!this.chessboard.state.extensionPoints[name]) {
23
+ this.chessboard.state.extensionPoints[name] = []
24
+ }
25
+ this.chessboard.state.extensionPoints[name].push(callback)
26
+ }
27
+
28
+ }
@@ -7,6 +7,7 @@
7
7
  import {VisualMoveInput} from "./VisualMoveInput.js"
8
8
  import {COLOR, INPUT_EVENT_TYPE, BORDER_TYPE} from "../Chessboard.js"
9
9
  import {Position} from "../model/Position.js"
10
+ import {EXTENSION_POINT} from "../model/Extension.js"
10
11
 
11
12
  export const piecesTranslations = {
12
13
  en: {
@@ -81,7 +82,7 @@ export class ChessboardView {
81
82
  this.createSvgAndGroups()
82
83
  this.updateMetrics()
83
84
  this.handleResize()
84
- this.redraw()
85
+ this.redrawBoard()
85
86
  }
86
87
 
87
88
  pointerDownHandler(e) {
@@ -165,15 +166,15 @@ export class ChessboardView {
165
166
  if (this.context.clientWidth !== this.width ||
166
167
  this.context.clientHeight !== this.height) {
167
168
  this.updateMetrics()
168
- this.redraw()
169
+ this.redrawBoard()
169
170
  this.redrawPieces()
170
171
  }
171
172
  this.svg.setAttribute("width", "100%") // safari bugfix
172
173
  this.svg.setAttribute("height", "100%")
173
174
  }
174
175
 
175
- redraw() {
176
- this.drawBoard()
176
+ redrawBoard() {
177
+ this.redrawSquares()
177
178
  this.drawCoordinates()
178
179
  this.drawMarkers()
179
180
  this.visualizeInputState()
@@ -181,7 +182,7 @@ export class ChessboardView {
181
182
 
182
183
  // Board //
183
184
 
184
- drawBoard() {
185
+ redrawSquares() {
185
186
  while (this.boardGroup.firstChild) {
186
187
  this.boardGroup.removeChild(this.boardGroup.lastChild)
187
188
  }
@@ -383,39 +384,45 @@ export class ChessboardView {
383
384
  // callbacks //
384
385
 
385
386
  moveStartCallback(square) {
387
+ const data = {
388
+ chessboard: this.chessboard,
389
+ type: INPUT_EVENT_TYPE.moveStart,
390
+ square: square
391
+ }
392
+ this.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.moveInputStateChanged, data)
386
393
  if (this.moveInputCallback) {
387
- return this.moveInputCallback({
388
- chessboard: this.chessboard,
389
- type: INPUT_EVENT_TYPE.moveStart,
390
- square: square
391
- })
394
+ return this.moveInputCallback(data)
392
395
  } else {
393
396
  return true
394
397
  }
395
398
  }
396
399
 
397
400
  moveDoneCallback(squareFrom, squareTo) {
401
+ const data = {
402
+ chessboard: this.chessboard,
403
+ type: INPUT_EVENT_TYPE.moveDone,
404
+ squareFrom: squareFrom,
405
+ squareTo: squareTo
406
+ }
407
+ this.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.moveInputStateChanged, data)
398
408
  if (this.moveInputCallback) {
399
- return this.moveInputCallback({
400
- chessboard: this.chessboard,
401
- type: INPUT_EVENT_TYPE.moveDone,
402
- squareFrom: squareFrom,
403
- squareTo: squareTo
404
- })
409
+ return this.moveInputCallback(data)
405
410
  } else {
406
411
  return true
407
412
  }
408
413
  }
409
414
 
410
415
  moveCanceledCallback(reason, squareFrom, squareTo) {
416
+ const data = {
417
+ chessboard: this.chessboard,
418
+ type: INPUT_EVENT_TYPE.moveCanceled,
419
+ reason: reason,
420
+ squareFrom: squareFrom,
421
+ squareTo: squareTo
422
+ }
423
+ this.chessboard.state.invokeExtensionPoints(EXTENSION_POINT.moveInputStateChanged, data)
411
424
  if (this.moveInputCallback) {
412
- this.moveInputCallback({
413
- chessboard: this.chessboard,
414
- type: INPUT_EVENT_TYPE.moveCanceled,
415
- reason: reason,
416
- squareFrom: squareFrom,
417
- squareTo: squareTo
418
- })
425
+ this.moveInputCallback(data)
419
426
  }
420
427
  }
421
428
 
@@ -259,7 +259,7 @@ export class PositionAnimationsQueue extends PromiseQueue {
259
259
  position, emptyPosition, animated ? duration : 0,
260
260
  () => {
261
261
  this.chessboard.state.orientation = color
262
- this.chessboard.view.redraw()
262
+ this.chessboard.view.redrawBoard()
263
263
  this.chessboard.view.redrawPieces(emptyPosition.squares)
264
264
  new PositionsAnimation(this.chessboard.view,
265
265
  emptyPosition, position, animated ? duration : 0,