cm-chessboard 8.7.9 → 8.7.11

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.
@@ -1,19 +1,14 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <!--
3
- License: Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
4
- https://creativecommons.org/licenses/by-sa/3.0/
5
-
6
- Author: Barak Michener
7
- -->
8
-
9
- <svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg">
10
- <title>cm-chessboard arrowheads</title>
11
- <desc>Arrowheads for cm-chessboard (https://shaack.com/projekte/cm-chessboard/)</desc>
12
- <g id="arrowDefault" transform="matrix(5.1931792,0,0,5.1931792,3.5980168,-0.83225447)">
13
- <path d="M 0.535156,0.398438 C 0.398438,0.507812 0.316406,0.671875 0.3125,0.847656 0.316406,1.02344 0.398438,1.1875 0.535156,1.29688 L 3.01172,3.35938 3.72266,4.01563 3.01172,4.64453 0.535156,6.67969 C 0.394531,6.79297 0.3125,6.96094 0.3125,7.13672 c 0,0.17578 0.082031,0.34375 0.222656,0.45703 0.328125,0.25781 0.792974,0.25781 1.117184,0 L 5.45703,4.46094 C 5.59766,4.34766 5.68359,4.17969 5.69141,4 5.69141,3.82031 5.60547,3.65234 5.45703,3.54297 L 1.64453,0.394531 C 1.31641,0.148438 0.859375,0.148438 0.535156,0.398438 Z m 0,0"
14
- class="head-fill"/>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="40px" height="40px" viewBox="0 0 40 40" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>arrows</title>
4
+ <g id="arrows" stroke="none" fill="none" fill-rule="evenodd" stroke-width="1">
5
+ <g id="cm-chessboard-arrowheads" transform="translate(5, 1)" fill="#000000">
6
+ <g id="arrowPointy" fill-rule="nonzero">
7
+ <path d="M13.5896955,18.4060262 L3.04774642,4.12789972 C2.71970374,3.68359527 2.81395252,3.05748421 3.25825697,2.72944153 C3.58591001,2.48752592 4.02754261,2.46849386 4.37479285,2.68132465 L29.6089137,18.1473987 C30.0797924,18.4360018 30.227556,19.0516834 29.9389529,19.5225621 C29.8565135,19.6570685 29.7434202,19.7701618 29.6089137,19.8526013 L4.37479285,35.3186754 C3.90391418,35.6072784 3.28823259,35.4595148 2.99962954,34.9886362 C2.78679874,34.6413859 2.80583081,34.1997533 3.04774642,33.8721003 L13.5896955,19.5939738 C13.850383,19.2408959 13.850383,18.7591041 13.5896955,18.4060262 Z" id="Path"></path>
8
+ </g>
9
+ <g id="arrowDefault" transform="translate(3, 0)" fill-rule="nonzero">
10
+ <path d="M13.6380471,11.3573439 L23.0265544,19.1369941 C23.4518122,19.4893778 23.5108883,20.1197808 23.1585046,20.5450386 C23.1090352,20.6047384 23.0527907,20.6584826 22.9909045,20.705188 L13.6023972,27.7906743 C13.1615654,28.1233691 12.5344983,28.0357067 12.2018035,27.594875 C12.0708444,27.4213498 12,27.2098744 12,26.9924778 L12,12.1273413 C12,11.5750565 12.4477153,11.1273413 13,11.1273413 C13.2329818,11.1273413 13.4586517,11.2086906 13.6380471,11.3573439 Z" id="Path"></path>
11
+ </g>
12
+ </g>
15
13
  </g>
16
- <g id="arrowPointy">
17
- <path d="M 20,20 5,5 35,20 5,35 Z" class="head-fill"/>
18
- </g>
19
- </svg>
14
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cm-chessboard",
3
- "version": "8.7.9",
3
+ "version": "8.7.11",
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.55
27
33
  }
28
34
  Object.assign(this.props, props)
29
35
  if (this.chessboard.props.assetsCache) {
@@ -70,12 +76,29 @@ 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
77
84
 
78
- const width = ((view.scalingX + view.scalingY) / 2) * 4
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
79
102
  let lineFill = Svg.addElement(arrowsGroup, "line")
80
103
  lineFill.setAttribute('x1', x1.toString())
81
104
  lineFill.setAttribute('x2', x2.toString())
@@ -9,8 +9,14 @@
9
9
  * Redrawing the same arrow or marker removes it.
10
10
  */
11
11
  import {Extension, EXTENSION_POINT} from "../../model/Extension.js"
12
- import {Arrows, ARROW_TYPE} from "../arrows/Arrows.js"
12
+ import {Arrows} from "../arrows/Arrows.js"
13
13
  import {Markers, MARKER_TYPE} from "../markers/Markers.js"
14
+ import {Svg} from "../../lib/Svg.js"
15
+
16
+ export const ARROW_TYPE = {
17
+ default: {class: "arrow-default", slice: "arrowDefault", headSize: 7},
18
+ danger: {class: "arrow-danger", slice: "arrowDefault", headSize: 7}
19
+ }
14
20
 
15
21
  // Define persistent type objects for matching (strict equality used in core extensions)
16
22
  const ARROW_GREEN = {class: "arrow-green", slice: "arrowDefault", headSize: 7}
@@ -34,19 +40,33 @@ export class RightClickAnnotator extends Extension {
34
40
 
35
41
  this.onContextMenu = this.onContextMenu.bind(this)
36
42
  this.onMouseDown = this.onMouseDown.bind(this)
43
+ this.onMouseMove = this.onMouseMove.bind(this)
37
44
  this.onMouseUp = this.onMouseUp.bind(this)
38
45
 
39
46
  this.dragStart = undefined // {square, modifiers}
47
+ this.previewGroup = undefined
48
+ this.previewActiveTo = undefined // cache last to-square for preview
40
49
 
41
50
  this.chessboard.context.addEventListener("contextmenu", this.onContextMenu)
42
51
  this.chessboard.context.addEventListener("mousedown", this.onMouseDown)
52
+ this.chessboard.context.addEventListener("mousemove", this.onMouseMove)
43
53
  this.chessboard.context.addEventListener("mouseup", this.onMouseUp)
54
+ this.chessboard.context.addEventListener("mouseleave", this.onMouseUp)
55
+
56
+ // ensure preview group exists after redraws
57
+ this.registerExtensionPoint(EXTENSION_POINT.afterRedrawBoard, () => {
58
+ this.ensurePreviewGroup()
59
+ })
44
60
 
45
61
  this.registerExtensionPoint(EXTENSION_POINT.destroy, () => {
46
62
  this.chessboard.context.removeEventListener("contextmenu", this.onContextMenu)
47
63
  this.chessboard.context.removeEventListener("mousedown", this.onMouseDown)
64
+ this.chessboard.context.removeEventListener("mousemove", this.onMouseMove)
48
65
  this.chessboard.context.removeEventListener("mouseup", this.onMouseUp)
66
+ this.chessboard.context.removeEventListener("mouseleave", this.onMouseUp)
49
67
  })
68
+ // create preview group now
69
+ this.ensurePreviewGroup()
50
70
  }
51
71
 
52
72
  onContextMenu(event) {
@@ -73,12 +93,11 @@ export class RightClickAnnotator extends Extension {
73
93
  }
74
94
 
75
95
  onMouseUp(event) {
76
- if (event.button !== 2) {
77
- return
78
- }
96
+ // clear preview regardless of button, but only act on right-button release
79
97
  const start = this.dragStart
80
98
  this.dragStart = undefined
81
- if (!start) {
99
+ this.clearPreview()
100
+ if (!start || event.button !== 2) {
82
101
  return
83
102
  }
84
103
  const endSquare = this.findSquareFromEvent(event) || start.square
@@ -91,6 +110,7 @@ export class RightClickAnnotator extends Extension {
91
110
  if (existing && existing.length > 0) {
92
111
  this.chessboard.removeArrows(arrowType, start.square, endSquare)
93
112
  } else {
113
+ this.chessboard.removeArrows(undefined, start.square, endSquare)
94
114
  this.chessboard.addArrow(arrowType, start.square, endSquare)
95
115
  }
96
116
  } else if (start.square) {
@@ -99,6 +119,7 @@ export class RightClickAnnotator extends Extension {
99
119
  if (existingMarkers && existingMarkers.length > 0) {
100
120
  this.chessboard.removeMarkers(circleType, start.square)
101
121
  } else {
122
+ this.chessboard.removeMarkers(undefined, start.square)
102
123
  this.chessboard.addMarker(circleType, start.square)
103
124
  }
104
125
  }
@@ -114,6 +135,104 @@ export class RightClickAnnotator extends Extension {
114
135
  return el ? el.getAttribute("data-square") : undefined
115
136
  }
116
137
 
138
+ ensurePreviewGroup() {
139
+ // create or reset preview group used for transient arrow drawing
140
+ if (!this.chessboard?.view?.markersTopLayer) {
141
+ return
142
+ }
143
+ if (this.previewGroup && this.previewGroup.parentNode) {
144
+ // keep and clear existing
145
+ this.clearPreview()
146
+ return
147
+ }
148
+ this.previewGroup = Svg.addElement(this.chessboard.view.markersTopLayer, "g", {class: "right-click-annotator-preview"})
149
+ }
150
+
151
+ clearPreview() {
152
+ if (!this.previewGroup) return
153
+ while (this.previewGroup.firstChild) {
154
+ this.previewGroup.removeChild(this.previewGroup.firstChild)
155
+ }
156
+ this.previewActiveTo = 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
+ this.clearPreview()
168
+ return
169
+ }
170
+ if (this.previewActiveTo === toSquare) {
171
+ return // no change
172
+ }
173
+ this.previewActiveTo = toSquare
174
+ const colorKey = this.modifiersToColorKey(this.dragStart.modifiers)
175
+ const {arrowType} = this.typesForColorKey(colorKey)
176
+ this.drawPreviewArrow(this.dragStart.square, toSquare, arrowType)
177
+ }
178
+
179
+ drawPreviewArrow(from, to, type) {
180
+ this.ensurePreviewGroup()
181
+ this.clearPreview()
182
+ const view = this.chessboard.view
183
+ const arrowsGroup = Svg.addElement(this.previewGroup, "g", {class: "arrow " + type.class})
184
+ const ptFrom = view.squareToPoint(from)
185
+ const ptTo = view.squareToPoint(to)
186
+ const defs = Svg.addElement(arrowsGroup, "defs")
187
+ const id = "arrow-preview-" + from + to
188
+ const marker = Svg.addElement(defs, "marker", {
189
+ id: id,
190
+ markerWidth: type.headSize,
191
+ markerHeight: type.headSize,
192
+ refX: 20,
193
+ refY: 20,
194
+ viewBox: "0 0 40 40",
195
+ orient: "auto",
196
+ class: "arrow-head",
197
+ })
198
+ const spriteUrl = this.chessboard.props.assetsCache ? "" : this.chessboard.getExtension(Arrows)?.getSpriteUrl?.() || this.chessboard.props.assetsUrl + "extensions/arrows/arrows.svg"
199
+ Svg.addElement(marker, "use", {href: `${spriteUrl}#${type.slice}`})
200
+ // Compute centers of start and end squares
201
+ const cx1 = ptFrom.x + view.squareWidth / 2
202
+ const cy1 = ptFrom.y + view.squareHeight / 2
203
+ const cx2 = ptTo.x + view.squareWidth / 2
204
+ const cy2 = ptTo.y + view.squareHeight / 2
205
+
206
+ // Offset the line so it starts/ends at the edge of an invisible circle inside each square
207
+ const dx = cx2 - cx1
208
+ const dy = cy2 - cy1
209
+ const len = Math.hypot(dx, dy) || 1
210
+ const ux = dx / len
211
+ const uy = dy / len
212
+ // get offsets from Arrows extension props to match final arrow rendering
213
+ const arrowsExt = this.chessboard.getExtension(Arrows)
214
+ const offsetFrom = arrowsExt?.props?.offsetFrom ?? 0.72
215
+ const offsetTo = arrowsExt?.props?.offsetTo ?? 0.72
216
+ const halfMin = Math.min(view.squareWidth, view.squareHeight) / 2
217
+ const clamp01 = (v) => Math.max(0, Math.min(1, v))
218
+ const rFrom = halfMin * clamp01(offsetFrom)
219
+ const rTo = halfMin * clamp01(offsetTo)
220
+ const x1 = cx1 + ux * rFrom
221
+ const y1 = cy1 + uy * rFrom
222
+ const x2 = cx2 - ux * rTo
223
+ const y2 = cy2 - uy * rTo
224
+
225
+ const width = ((view.scalingX + view.scalingY) / 2) * 8
226
+ let lineFill = Svg.addElement(arrowsGroup, "line")
227
+ lineFill.setAttribute('x1', x1.toString())
228
+ lineFill.setAttribute('x2', x2.toString())
229
+ lineFill.setAttribute('y1', y1.toString())
230
+ lineFill.setAttribute('y2', y2.toString())
231
+ lineFill.setAttribute('class', 'arrow-line')
232
+ lineFill.setAttribute("marker-end", "url(#" + id + ")")
233
+ lineFill.setAttribute('stroke-width', width + "px")
234
+ }
235
+
117
236
  modifiersToColorKey(modifiers) {
118
237
  if (modifiers.shift && modifiers.alt) return "orange"
119
238
  if (modifiers.shift) return "red"