cm-chessboard 8.7.9 → 8.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cm-chessboard",
3
- "version": "8.7.9",
3
+ "version": "8.7.10",
4
4
  "description": "A JavaScript chessboard which is lightweight, ES6 module based, responsive, SVG rendered and without dependencies.",
5
5
  "keywords": [
6
6
  "chess",
@@ -23,7 +23,13 @@ export class Arrows extends Extension {
23
23
  this.onRedrawBoard()
24
24
  })
25
25
  this.props = {
26
- sprite: "extensions/arrows/arrows.svg"
26
+ sprite: "extensions/arrows/arrows.svg",
27
+ // offset factors relative to half the square size (0.0 .. 1.0)
28
+ // 0.0 = start/end in the center; 1.0 = at the outer edge of the inscribed circle (half the square)
29
+ // default chosen to preserve previous appearance where radius = 0.36 * min(squareWidth, squareHeight)
30
+ // which corresponds to 0.72 of half the square size (0.36 / 0.5 = 0.72)
31
+ offsetFrom: 0,
32
+ offsetTo: 0.72
27
33
  }
28
34
  Object.assign(this.props, props)
29
35
  if (this.chessboard.props.assetsCache) {
@@ -70,10 +76,27 @@ export class Arrows extends Extension {
70
76
  href: `${spriteUrl}#${arrow.type.slice}`,
71
77
  })
72
78
 
73
- const x1 = ptFrom.x + view.squareWidth / 2
74
- const x2 = ptTo.x + view.squareHeight / 2
75
- const y1 = ptFrom.y + view.squareWidth / 2
76
- const y2 = ptTo.y + view.squareHeight / 2
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
77
100
 
78
101
  const width = ((view.scalingX + view.scalingY) / 2) * 4
79
102
  let lineFill = Svg.addElement(arrowsGroup, "line")
@@ -11,6 +11,7 @@
11
11
  import {Extension, EXTENSION_POINT} from "../../model/Extension.js"
12
12
  import {Arrows, ARROW_TYPE} from "../arrows/Arrows.js"
13
13
  import {Markers, MARKER_TYPE} from "../markers/Markers.js"
14
+ import {Svg} from "../../lib/Svg.js"
14
15
 
15
16
  // Define persistent type objects for matching (strict equality used in core extensions)
16
17
  const ARROW_GREEN = {class: "arrow-green", slice: "arrowDefault", headSize: 7}
@@ -34,19 +35,33 @@ export class RightClickAnnotator extends Extension {
34
35
 
35
36
  this.onContextMenu = this.onContextMenu.bind(this)
36
37
  this.onMouseDown = this.onMouseDown.bind(this)
38
+ this.onMouseMove = this.onMouseMove.bind(this)
37
39
  this.onMouseUp = this.onMouseUp.bind(this)
38
40
 
39
41
  this.dragStart = undefined // {square, modifiers}
42
+ this.previewGroup = undefined
43
+ this.previewActiveTo = undefined // cache last to-square for preview
40
44
 
41
45
  this.chessboard.context.addEventListener("contextmenu", this.onContextMenu)
42
46
  this.chessboard.context.addEventListener("mousedown", this.onMouseDown)
47
+ this.chessboard.context.addEventListener("mousemove", this.onMouseMove)
43
48
  this.chessboard.context.addEventListener("mouseup", this.onMouseUp)
49
+ this.chessboard.context.addEventListener("mouseleave", this.onMouseUp)
50
+
51
+ // ensure preview group exists after redraws
52
+ this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, () => {
53
+ this.ensurePreviewGroup()
54
+ })
44
55
 
45
56
  this.registerExtensionPoint(EXTENSION_POINT.destroy, () => {
46
57
  this.chessboard.context.removeEventListener("contextmenu", this.onContextMenu)
47
58
  this.chessboard.context.removeEventListener("mousedown", this.onMouseDown)
59
+ this.chessboard.context.removeEventListener("mousemove", this.onMouseMove)
48
60
  this.chessboard.context.removeEventListener("mouseup", this.onMouseUp)
61
+ this.chessboard.context.removeEventListener("mouseleave", this.onMouseUp)
49
62
  })
63
+ // create preview group now
64
+ this.ensurePreviewGroup()
50
65
  }
51
66
 
52
67
  onContextMenu(event) {
@@ -73,12 +88,11 @@ export class RightClickAnnotator extends Extension {
73
88
  }
74
89
 
75
90
  onMouseUp(event) {
76
- if (event.button !== 2) {
77
- return
78
- }
91
+ // clear preview regardless of button, but only act on right-button release
79
92
  const start = this.dragStart
80
93
  this.dragStart = undefined
81
- if (!start) {
94
+ this.clearPreview()
95
+ if (!start || event.button !== 2) {
82
96
  return
83
97
  }
84
98
  const endSquare = this.findSquareFromEvent(event) || start.square
@@ -91,6 +105,7 @@ export class RightClickAnnotator extends Extension {
91
105
  if (existing && existing.length > 0) {
92
106
  this.chessboard.removeArrows(arrowType, start.square, endSquare)
93
107
  } else {
108
+ this.chessboard.removeArrows(undefined, start.square, endSquare)
94
109
  this.chessboard.addArrow(arrowType, start.square, endSquare)
95
110
  }
96
111
  } else if (start.square) {
@@ -99,6 +114,7 @@ export class RightClickAnnotator extends Extension {
99
114
  if (existingMarkers && existingMarkers.length > 0) {
100
115
  this.chessboard.removeMarkers(circleType, start.square)
101
116
  } else {
117
+ this.chessboard.removeMarkers(undefined, start.square)
102
118
  this.chessboard.addMarker(circleType, start.square)
103
119
  }
104
120
  }
@@ -114,6 +130,104 @@ export class RightClickAnnotator extends Extension {
114
130
  return el ? el.getAttribute("data-square") : undefined
115
131
  }
116
132
 
133
+ ensurePreviewGroup() {
134
+ // create or reset preview group used for transient arrow drawing
135
+ if (!this.chessboard?.view?.markersTopLayer) {
136
+ return
137
+ }
138
+ if (this.previewGroup && this.previewGroup.parentNode) {
139
+ // keep and clear existing
140
+ this.clearPreview()
141
+ return
142
+ }
143
+ this.previewGroup = Svg.addElement(this.chessboard.view.markersTopLayer, "g", {class: "right-click-annotator-preview"})
144
+ }
145
+
146
+ clearPreview() {
147
+ if (!this.previewGroup) return
148
+ while (this.previewGroup.firstChild) {
149
+ this.previewGroup.removeChild(this.previewGroup.firstChild)
150
+ }
151
+ this.previewActiveTo = undefined
152
+ }
153
+
154
+ onMouseMove(event) {
155
+ if (!this.dragStart) {
156
+ return
157
+ }
158
+ // Only show preview for right-button drag if still pressed (best-effort); some browsers may not keep buttons state reliably
159
+ // We rely mainly on our dragStart flag and clear on mouseup/mouseleave
160
+ const toSquare = this.findSquareFromEvent(event)
161
+ if (!toSquare || toSquare === this.dragStart.square) {
162
+ this.clearPreview()
163
+ return
164
+ }
165
+ if (this.previewActiveTo === toSquare) {
166
+ return // no change
167
+ }
168
+ this.previewActiveTo = toSquare
169
+ const colorKey = this.modifiersToColorKey(this.dragStart.modifiers)
170
+ const {arrowType} = this.typesForColorKey(colorKey)
171
+ this.drawPreviewArrow(this.dragStart.square, toSquare, arrowType)
172
+ }
173
+
174
+ drawPreviewArrow(from, to, type) {
175
+ this.ensurePreviewGroup()
176
+ this.clearPreview()
177
+ const view = this.chessboard.view
178
+ const arrowsGroup = Svg.addElement(this.previewGroup, "g", {class: "arrow " + type.class})
179
+ const ptFrom = view.squareToPoint(from)
180
+ const ptTo = view.squareToPoint(to)
181
+ const defs = Svg.addElement(arrowsGroup, "defs")
182
+ const id = "arrow-preview-" + from + to
183
+ const marker = Svg.addElement(defs, "marker", {
184
+ id: id,
185
+ markerWidth: type.headSize,
186
+ markerHeight: type.headSize,
187
+ refX: 20,
188
+ refY: 20,
189
+ viewBox: "0 0 40 40",
190
+ orient: "auto",
191
+ class: "arrow-head",
192
+ })
193
+ const spriteUrl = this.chessboard.props.assetsCache ? "" : this.chessboard.getExtension(Arrows)?.getSpriteUrl?.() || this.chessboard.props.assetsUrl + "extensions/arrows/arrows.svg"
194
+ Svg.addElement(marker, "use", {href: `${spriteUrl}#${type.slice}`})
195
+ // Compute centers of start and end squares
196
+ const cx1 = ptFrom.x + view.squareWidth / 2
197
+ const cy1 = ptFrom.y + view.squareHeight / 2
198
+ const cx2 = ptTo.x + view.squareWidth / 2
199
+ const cy2 = ptTo.y + view.squareHeight / 2
200
+
201
+ // Offset the line so it starts/ends at the edge of an invisible circle inside each square
202
+ const dx = cx2 - cx1
203
+ const dy = cy2 - cy1
204
+ const len = Math.hypot(dx, dy) || 1
205
+ const ux = dx / len
206
+ const uy = dy / len
207
+ // get offsets from Arrows extension props to match final arrow rendering
208
+ const arrowsExt = this.chessboard.getExtension(Arrows)
209
+ const offsetFrom = arrowsExt?.props?.offsetFrom ?? 0.72
210
+ const offsetTo = arrowsExt?.props?.offsetTo ?? 0.72
211
+ const halfMin = Math.min(view.squareWidth, view.squareHeight) / 2
212
+ const clamp01 = (v) => Math.max(0, Math.min(1, v))
213
+ const rFrom = halfMin * clamp01(offsetFrom)
214
+ const rTo = halfMin * clamp01(offsetTo)
215
+ const x1 = cx1 + ux * rFrom
216
+ const y1 = cy1 + uy * rFrom
217
+ const x2 = cx2 - ux * rTo
218
+ const y2 = cy2 - uy * rTo
219
+
220
+ const width = ((view.scalingX + view.scalingY) / 2) * 4
221
+ let lineFill = Svg.addElement(arrowsGroup, "line")
222
+ lineFill.setAttribute('x1', x1.toString())
223
+ lineFill.setAttribute('x2', x2.toString())
224
+ lineFill.setAttribute('y1', y1.toString())
225
+ lineFill.setAttribute('y2', y2.toString())
226
+ lineFill.setAttribute('class', 'arrow-line')
227
+ lineFill.setAttribute("marker-end", "url(#" + id + ")")
228
+ lineFill.setAttribute('stroke-width', width + "px")
229
+ }
230
+
117
231
  modifiersToColorKey(modifiers) {
118
232
  if (modifiers.shift && modifiers.alt) return "orange"
119
233
  if (modifiers.shift) return "red"