@lichess-org/chessground 9.2.3

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.
Files changed (87) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +110 -0
  3. package/assets/chessground.base.css +224 -0
  4. package/assets/chessground.brown.css +62 -0
  5. package/assets/chessground.cburnett.css +37 -0
  6. package/dist/anim.d.ts +17 -0
  7. package/dist/anim.js +99 -0
  8. package/dist/anim.js.map +1 -0
  9. package/dist/api.d.ts +28 -0
  10. package/dist/api.js +98 -0
  11. package/dist/api.js.map +1 -0
  12. package/dist/autoPieces.d.ts +3 -0
  13. package/dist/autoPieces.js +38 -0
  14. package/dist/autoPieces.js.map +1 -0
  15. package/dist/board.d.ts +25 -0
  16. package/dist/board.js +331 -0
  17. package/dist/board.js.map +1 -0
  18. package/dist/chessground.d.ts +7 -0
  19. package/dist/chessground.js +63 -0
  20. package/dist/chessground.js.map +1 -0
  21. package/dist/chessground.min.js +1 -0
  22. package/dist/config.d.ts +87 -0
  23. package/dist/config.js +64 -0
  24. package/dist/config.js.map +1 -0
  25. package/dist/drag.d.ts +20 -0
  26. package/dist/drag.js +208 -0
  27. package/dist/drag.js.map +1 -0
  28. package/dist/draw.d.ts +65 -0
  29. package/dist/draw.js +90 -0
  30. package/dist/draw.js.map +1 -0
  31. package/dist/drop.d.ts +5 -0
  32. package/dist/drop.js +31 -0
  33. package/dist/drop.js.map +1 -0
  34. package/dist/events.d.ts +4 -0
  35. package/dist/events.js +72 -0
  36. package/dist/events.js.map +1 -0
  37. package/dist/explosion.d.ts +3 -0
  38. package/dist/explosion.js +18 -0
  39. package/dist/explosion.js.map +1 -0
  40. package/dist/fen.d.ts +4 -0
  41. package/dist/fen.js +79 -0
  42. package/dist/fen.js.map +1 -0
  43. package/dist/premove.d.ts +6 -0
  44. package/dist/premove.js +57 -0
  45. package/dist/premove.js.map +1 -0
  46. package/dist/render.d.ts +4 -0
  47. package/dist/render.js +235 -0
  48. package/dist/render.js.map +1 -0
  49. package/dist/state.d.ts +100 -0
  50. package/dist/state.js +92 -0
  51. package/dist/state.js.map +1 -0
  52. package/dist/svg.d.ts +8 -0
  53. package/dist/svg.js +348 -0
  54. package/dist/svg.js.map +1 -0
  55. package/dist/sync.d.ts +8 -0
  56. package/dist/sync.js +27 -0
  57. package/dist/sync.js.map +1 -0
  58. package/dist/types.d.ts +94 -0
  59. package/dist/types.js +5 -0
  60. package/dist/types.js.map +1 -0
  61. package/dist/util.d.ts +20 -0
  62. package/dist/util.js +89 -0
  63. package/dist/util.js.map +1 -0
  64. package/dist/wrap.d.ts +3 -0
  65. package/dist/wrap.js +90 -0
  66. package/dist/wrap.js.map +1 -0
  67. package/package.json +58 -0
  68. package/src/anim.ts +139 -0
  69. package/src/api.ts +187 -0
  70. package/src/autoPieces.ts +47 -0
  71. package/src/board.ts +371 -0
  72. package/src/chessground.ts +67 -0
  73. package/src/config.ts +165 -0
  74. package/src/drag.ts +223 -0
  75. package/src/draw.ts +154 -0
  76. package/src/drop.ts +36 -0
  77. package/src/events.ts +86 -0
  78. package/src/explosion.ts +19 -0
  79. package/src/fen.ts +78 -0
  80. package/src/premove.ts +76 -0
  81. package/src/render.ts +262 -0
  82. package/src/state.ts +199 -0
  83. package/src/svg.ts +441 -0
  84. package/src/sync.ts +36 -0
  85. package/src/types.ts +110 -0
  86. package/src/util.ts +105 -0
  87. package/src/wrap.ts +111 -0
package/src/config.ts ADDED
@@ -0,0 +1,165 @@
1
+ import { HeadlessState } from './state.js';
2
+ import { setCheck, setSelected } from './board.js';
3
+ import { read as fenRead } from './fen.js';
4
+ import { DrawShape, DrawBrushes } from './draw.js';
5
+ import * as cg from './types.js';
6
+
7
+ export interface Config {
8
+ fen?: cg.FEN; // chess position in Forsyth notation
9
+ orientation?: cg.Color; // board orientation. white | black
10
+ turnColor?: cg.Color; // turn to play. white | black
11
+ check?: cg.Color | boolean; // true for current color, false to unset
12
+ lastMove?: cg.Key[]; // squares part of the last move ["c3", "c4"]
13
+ selected?: cg.Key; // square currently selected "a1"
14
+ coordinates?: boolean; // include coords attributes
15
+ coordinatesOnSquares?: boolean; // include coords attributes on every square
16
+ autoCastle?: boolean; // immediately complete the castle by moving the rook after king move
17
+ viewOnly?: boolean; // don't bind events: the user will never be able to move pieces around
18
+ disableContextMenu?: boolean; // because who needs a context menu on a chessboard
19
+ addPieceZIndex?: boolean; // adds z-index values to pieces (for 3D)
20
+ addDimensionsCssVarsTo?: HTMLElement; // add ---cg-width and ---cg-height CSS vars containing the board's dimensions to this element
21
+ blockTouchScroll?: boolean; // block scrolling via touch dragging on the board, e.g. for coordinate training
22
+ // pieceKey: boolean; // add a data-key attribute to piece elements
23
+ trustAllEvents?: boolean; // disable checking for human only input (e.isTrusted)
24
+ highlight?: {
25
+ lastMove?: boolean; // add last-move class to squares
26
+ check?: boolean; // add check class to squares
27
+ custom?: cg.SquareClasses; // add custom classes to custom squares
28
+ };
29
+ animation?: {
30
+ enabled?: boolean;
31
+ duration?: number;
32
+ };
33
+ movable?: {
34
+ free?: boolean; // all moves are valid - board editor
35
+ color?: cg.Color | 'both'; // color that can move. white | black | both | undefined
36
+ dests?: cg.Dests; // valid moves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
37
+ showDests?: boolean; // whether to add the move-dest class on squares
38
+ events?: {
39
+ after?: (orig: cg.Key, dest: cg.Key, metadata: cg.MoveMetadata) => void; // called after the move has been played
40
+ afterNewPiece?: (role: cg.Role, key: cg.Key, metadata: cg.MoveMetadata) => void; // called after a new piece is dropped on the board
41
+ };
42
+ rookCastle?: boolean; // castle by moving the king to the rook
43
+ };
44
+ premovable?: {
45
+ enabled?: boolean; // allow premoves for color that can not move
46
+ showDests?: boolean; // whether to add the premove-dest class on squares
47
+ castle?: boolean; // whether to allow king castle premoves
48
+ dests?: cg.Key[]; // premove destinations for the current selection
49
+ customDests?: cg.Dests; // use custom valid premoves. {"a2" ["a3" "a4"] "b1" ["a3" "c3"]}
50
+ events?: {
51
+ set?: (orig: cg.Key, dest: cg.Key, metadata?: cg.SetPremoveMetadata) => void; // called after the premove has been set
52
+ unset?: () => void; // called after the premove has been unset
53
+ };
54
+ };
55
+ predroppable?: {
56
+ enabled?: boolean; // allow predrops for color that can not move
57
+ events?: {
58
+ set?: (role: cg.Role, key: cg.Key) => void; // called after the predrop has been set
59
+ unset?: () => void; // called after the predrop has been unset
60
+ };
61
+ };
62
+ draggable?: {
63
+ enabled?: boolean; // allow moves & premoves to use drag'n drop
64
+ distance?: number; // minimum distance to initiate a drag; in pixels
65
+ autoDistance?: boolean; // lets chessground set distance to zero when user drags pieces
66
+ showGhost?: boolean; // show ghost of piece being dragged
67
+ deleteOnDropOff?: boolean; // delete a piece when it is dropped off the board
68
+ };
69
+ selectable?: {
70
+ // disable to enforce dragging over click-click move
71
+ enabled?: boolean;
72
+ };
73
+ events?: {
74
+ change?: () => void; // called after the situation changes on the board
75
+ // called after a piece has been moved.
76
+ // capturedPiece is undefined or like {color: 'white'; 'role': 'queen'}
77
+ move?: (orig: cg.Key, dest: cg.Key, capturedPiece?: cg.Piece) => void;
78
+ dropNewPiece?: (piece: cg.Piece, key: cg.Key) => void;
79
+ select?: (key: cg.Key) => void; // called when a square is selected
80
+ insert?: (elements: cg.Elements) => void; // when the board DOM has been (re)inserted
81
+ };
82
+ drawable?: {
83
+ enabled?: boolean; // can draw
84
+ visible?: boolean; // can view
85
+ defaultSnapToValidMove?: boolean;
86
+ // false to keep the drawing if a movable piece is clicked.
87
+ // Clicking an empty square or immovable piece will clear the drawing regardless.
88
+ eraseOnClick?: boolean;
89
+ shapes?: DrawShape[];
90
+ autoShapes?: DrawShape[];
91
+ brushes?: DrawBrushes;
92
+ onChange?: (shapes: DrawShape[]) => void; // called after drawable shapes change
93
+ };
94
+ }
95
+
96
+ export function applyAnimation(state: HeadlessState, config: Config): void {
97
+ if (config.animation) {
98
+ deepMerge(state.animation, config.animation);
99
+ // no need for such short animations
100
+ if ((state.animation.duration || 0) < 70) state.animation.enabled = false;
101
+ }
102
+ }
103
+
104
+ export function configure(state: HeadlessState, config: Config): void {
105
+ // don't merge destinations and autoShapes. Just override.
106
+ if (config.movable?.dests) state.movable.dests = undefined;
107
+ if (config.drawable?.autoShapes) state.drawable.autoShapes = [];
108
+
109
+ deepMerge(state, config);
110
+
111
+ // if a fen was provided, replace the pieces
112
+ if (config.fen) {
113
+ state.pieces = fenRead(config.fen);
114
+ state.drawable.shapes = config.drawable?.shapes || [];
115
+ }
116
+
117
+ // apply config values that could be undefined yet meaningful
118
+ if ('check' in config) setCheck(state, config.check || false);
119
+ if ('lastMove' in config && !config.lastMove) state.lastMove = undefined;
120
+ // in case of ZH drop last move, there's a single square.
121
+ // if the previous last move had two squares,
122
+ // the merge algorithm will incorrectly keep the second square.
123
+ else if (config.lastMove) state.lastMove = config.lastMove;
124
+
125
+ // fix move/premove dests
126
+ if (state.selected) setSelected(state, state.selected);
127
+
128
+ applyAnimation(state, config);
129
+
130
+ if (!state.movable.rookCastle && state.movable.dests) {
131
+ const rank = state.movable.color === 'white' ? '1' : '8',
132
+ kingStartPos = ('e' + rank) as cg.Key,
133
+ dests = state.movable.dests.get(kingStartPos),
134
+ king = state.pieces.get(kingStartPos);
135
+ if (!dests || !king || king.role !== 'king') return;
136
+ state.movable.dests.set(
137
+ kingStartPos,
138
+ dests.filter(
139
+ d =>
140
+ !(d === 'a' + rank && dests.includes(('c' + rank) as cg.Key)) &&
141
+ !(d === 'h' + rank && dests.includes(('g' + rank) as cg.Key)),
142
+ ),
143
+ );
144
+ }
145
+ }
146
+
147
+ function deepMerge(base: any, extend: any): void {
148
+ for (const key in extend) {
149
+ if (key === '__proto__' || key === 'constructor' || !Object.prototype.hasOwnProperty.call(extend, key))
150
+ continue;
151
+ if (
152
+ Object.prototype.hasOwnProperty.call(base, key) &&
153
+ isPlainObject(base[key]) &&
154
+ isPlainObject(extend[key])
155
+ )
156
+ deepMerge(base[key], extend[key]);
157
+ else base[key] = extend[key];
158
+ }
159
+ }
160
+
161
+ function isPlainObject(o: unknown): boolean {
162
+ if (typeof o !== 'object' || o === null) return false;
163
+ const proto = Object.getPrototypeOf(o);
164
+ return proto === Object.prototype || proto === null;
165
+ }
package/src/drag.ts ADDED
@@ -0,0 +1,223 @@
1
+ import { State } from './state.js';
2
+ import * as board from './board.js';
3
+ import * as util from './util.js';
4
+ import { clear as drawClear } from './draw.js';
5
+ import * as cg from './types.js';
6
+ import { anim } from './anim.js';
7
+
8
+ export interface DragCurrent {
9
+ orig: cg.Key; // orig key of dragging piece
10
+ piece: cg.Piece;
11
+ origPos: cg.NumberPair; // first event position
12
+ pos: cg.NumberPair; // latest event position
13
+ started: boolean; // whether the drag has started; as per the distance setting
14
+ element: cg.PieceNode | (() => cg.PieceNode | undefined);
15
+ newPiece?: boolean; // it it a new piece from outside the board
16
+ force?: boolean; // can the new piece replace an existing one (editor)
17
+ previouslySelected?: cg.Key;
18
+ originTarget: EventTarget | null;
19
+ keyHasChanged: boolean; // whether the drag has left the orig key
20
+ }
21
+
22
+ export function start(s: State, e: cg.MouchEvent): void {
23
+ if (!(s.trustAllEvents || e.isTrusted)) return; // only trust when trustAllEvents is enabled
24
+ if (e.buttons !== undefined && e.buttons > 1) return; // only touch or left click
25
+ if (e.touches && e.touches.length > 1) return; // support one finger touch only
26
+ const bounds = s.dom.bounds(),
27
+ position = util.eventPosition(e)!,
28
+ orig = board.getKeyAtDomPos(position, board.whitePov(s), bounds);
29
+ if (!orig) return;
30
+ const piece = s.pieces.get(orig);
31
+ const previouslySelected = s.selected;
32
+ if (
33
+ !previouslySelected &&
34
+ s.drawable.enabled &&
35
+ (s.drawable.eraseOnClick || !piece || piece.color !== s.turnColor)
36
+ )
37
+ drawClear(s);
38
+ // Prevent touch scroll and create no corresponding mouse event, if there
39
+ // is an intent to interact with the board.
40
+ if (
41
+ e.cancelable !== false &&
42
+ (!e.touches || s.blockTouchScroll || piece || previouslySelected || pieceCloseTo(s, position))
43
+ )
44
+ e.preventDefault();
45
+ else if (e.touches) return; // Handle only corresponding mouse event https://github.com/lichess-org/chessground/pull/268
46
+
47
+ const hadPremove = !!s.premovable.current;
48
+ const hadPredrop = !!s.predroppable.current;
49
+ s.stats.ctrlKey = e.ctrlKey;
50
+ if (s.selected && board.canMove(s, s.selected, orig)) {
51
+ anim(state => board.selectSquare(state, orig), s);
52
+ } else {
53
+ board.selectSquare(s, orig);
54
+ }
55
+ const stillSelected = s.selected === orig;
56
+ const element = pieceElementByKey(s, orig);
57
+ if (piece && element && stillSelected && board.isDraggable(s, orig)) {
58
+ s.draggable.current = {
59
+ orig,
60
+ piece,
61
+ origPos: position,
62
+ pos: position,
63
+ started: s.draggable.autoDistance && s.stats.dragged,
64
+ element,
65
+ previouslySelected,
66
+ originTarget: e.target,
67
+ keyHasChanged: false,
68
+ };
69
+ element.cgDragging = true;
70
+ element.classList.add('dragging');
71
+ // place ghost
72
+ const ghost = s.dom.elements.ghost;
73
+ if (ghost) {
74
+ ghost.className = `ghost ${piece.color} ${piece.role}`;
75
+ util.translate(ghost, util.posToTranslate(bounds)(util.key2pos(orig), board.whitePov(s)));
76
+ util.setVisible(ghost, true);
77
+ }
78
+ processDrag(s);
79
+ } else {
80
+ if (hadPremove) board.unsetPremove(s);
81
+ if (hadPredrop) board.unsetPredrop(s);
82
+ }
83
+ s.dom.redraw();
84
+ }
85
+
86
+ function pieceCloseTo(s: State, pos: cg.NumberPair): boolean {
87
+ const asWhite = board.whitePov(s),
88
+ bounds = s.dom.bounds(),
89
+ radiusSq = Math.pow(bounds.width / 8, 2);
90
+ for (const key of s.pieces.keys()) {
91
+ const center = util.computeSquareCenter(key, asWhite, bounds);
92
+ if (util.distanceSq(center, pos) <= radiusSq) return true;
93
+ }
94
+ return false;
95
+ }
96
+
97
+ export function dragNewPiece(s: State, piece: cg.Piece, e: cg.MouchEvent, force?: boolean): void {
98
+ const key: cg.Key = 'a0';
99
+ s.pieces.set(key, piece);
100
+ s.dom.redraw();
101
+
102
+ const position = util.eventPosition(e)!;
103
+
104
+ s.draggable.current = {
105
+ orig: key,
106
+ piece,
107
+ origPos: position,
108
+ pos: position,
109
+ started: true,
110
+ element: () => pieceElementByKey(s, key),
111
+ originTarget: e.target,
112
+ newPiece: true,
113
+ force: !!force,
114
+ keyHasChanged: false,
115
+ };
116
+ processDrag(s);
117
+ }
118
+
119
+ function processDrag(s: State): void {
120
+ requestAnimationFrame(() => {
121
+ const cur = s.draggable.current;
122
+ if (!cur) return;
123
+ // cancel animations while dragging
124
+ if (s.animation.current?.plan.anims.has(cur.orig)) s.animation.current = undefined;
125
+ // if moving piece is gone, cancel
126
+ const origPiece = s.pieces.get(cur.orig);
127
+ if (!origPiece || !util.samePiece(origPiece, cur.piece)) cancel(s);
128
+ else {
129
+ if (!cur.started && util.distanceSq(cur.pos, cur.origPos) >= Math.pow(s.draggable.distance, 2))
130
+ cur.started = true;
131
+ if (cur.started) {
132
+ // support lazy elements
133
+ if (typeof cur.element === 'function') {
134
+ const found = cur.element();
135
+ if (!found) return;
136
+ found.cgDragging = true;
137
+ found.classList.add('dragging');
138
+ cur.element = found;
139
+ }
140
+
141
+ const bounds = s.dom.bounds();
142
+ util.translate(cur.element, [
143
+ cur.pos[0] - bounds.left - bounds.width / 16,
144
+ cur.pos[1] - bounds.top - bounds.height / 16,
145
+ ]);
146
+
147
+ cur.keyHasChanged ||= cur.orig !== board.getKeyAtDomPos(cur.pos, board.whitePov(s), bounds);
148
+ }
149
+ }
150
+ processDrag(s);
151
+ });
152
+ }
153
+
154
+ export function move(s: State, e: cg.MouchEvent): void {
155
+ // support one finger touch only
156
+ if (s.draggable.current && (!e.touches || e.touches.length < 2)) {
157
+ s.draggable.current.pos = util.eventPosition(e)!;
158
+ }
159
+ }
160
+
161
+ export function end(s: State, e: cg.MouchEvent): void {
162
+ const cur = s.draggable.current;
163
+ if (!cur) return;
164
+ // create no corresponding mouse event
165
+ if (e.type === 'touchend' && e.cancelable !== false) e.preventDefault();
166
+ // comparing with the origin target is an easy way to test that the end event
167
+ // has the same touch origin
168
+ if (e.type === 'touchend' && cur.originTarget !== e.target && !cur.newPiece) {
169
+ s.draggable.current = undefined;
170
+ return;
171
+ }
172
+ board.unsetPremove(s);
173
+ board.unsetPredrop(s);
174
+ // touchend has no position; so use the last touchmove position instead
175
+ const eventPos = util.eventPosition(e) || cur.pos;
176
+ const dest = board.getKeyAtDomPos(eventPos, board.whitePov(s), s.dom.bounds());
177
+ if (dest && cur.started && cur.orig !== dest) {
178
+ if (cur.newPiece) board.dropNewPiece(s, cur.orig, dest, cur.force);
179
+ else {
180
+ s.stats.ctrlKey = e.ctrlKey;
181
+ if (board.userMove(s, cur.orig, dest)) s.stats.dragged = true;
182
+ }
183
+ } else if (cur.newPiece) {
184
+ s.pieces.delete(cur.orig);
185
+ } else if (s.draggable.deleteOnDropOff && !dest) {
186
+ s.pieces.delete(cur.orig);
187
+ board.callUserFunction(s.events.change);
188
+ }
189
+ if ((cur.orig === cur.previouslySelected || cur.keyHasChanged) && (cur.orig === dest || !dest))
190
+ board.unselect(s);
191
+ else if (!s.selectable.enabled) board.unselect(s);
192
+
193
+ removeDragElements(s);
194
+
195
+ s.draggable.current = undefined;
196
+ s.dom.redraw();
197
+ }
198
+
199
+ export function cancel(s: State): void {
200
+ const cur = s.draggable.current;
201
+ if (cur) {
202
+ if (cur.newPiece) s.pieces.delete(cur.orig);
203
+ s.draggable.current = undefined;
204
+ board.unselect(s);
205
+ removeDragElements(s);
206
+ s.dom.redraw();
207
+ }
208
+ }
209
+
210
+ function removeDragElements(s: State): void {
211
+ const e = s.dom.elements;
212
+ if (e.ghost) util.setVisible(e.ghost, false);
213
+ }
214
+
215
+ function pieceElementByKey(s: State, key: cg.Key): cg.PieceNode | undefined {
216
+ let el = s.dom.elements.board.firstChild;
217
+ while (el) {
218
+ if ((el as cg.KeyedNode).cgKey === key && (el as cg.KeyedNode).tagName === 'PIECE')
219
+ return el as cg.PieceNode;
220
+ el = el.nextSibling;
221
+ }
222
+ return;
223
+ }
package/src/draw.ts ADDED
@@ -0,0 +1,154 @@
1
+ import { State } from './state.js';
2
+ import { unselect, cancelMove, getKeyAtDomPos, getSnappedKeyAtDomPos, whitePov } from './board.js';
3
+ import { eventPosition, isRightButton } from './util.js';
4
+ import * as cg from './types.js';
5
+
6
+ export interface DrawShape {
7
+ orig: cg.Key;
8
+ dest?: cg.Key;
9
+ brush?: string; // if no brush, no shape. label moved to top right of square
10
+ modifiers?: DrawModifiers;
11
+ piece?: DrawShapePiece;
12
+ customSvg?: { html: string; center?: 'orig' | 'dest' | 'label' }; // 100 x 100 viewbox cenetered at [50,50]
13
+ label?: { text: string; fill?: string }; // fill is in '#rrggbb' format
14
+ }
15
+
16
+ export interface DrawModifiers {
17
+ lineWidth?: number;
18
+ hilite?: boolean;
19
+ }
20
+
21
+ export interface DrawShapePiece {
22
+ role: cg.Role;
23
+ color: cg.Color;
24
+ scale?: number;
25
+ }
26
+
27
+ export interface DrawBrush {
28
+ key: string;
29
+ color: string;
30
+ opacity: number;
31
+ lineWidth: number;
32
+ }
33
+
34
+ export interface DrawBrushes {
35
+ green: DrawBrush;
36
+ red: DrawBrush;
37
+ blue: DrawBrush;
38
+ yellow: DrawBrush;
39
+ [color: string]: DrawBrush;
40
+ }
41
+
42
+ export interface Drawable {
43
+ enabled: boolean; // can draw
44
+ visible: boolean; // can view
45
+ defaultSnapToValidMove: boolean;
46
+ eraseOnClick: boolean;
47
+ onChange?: (shapes: DrawShape[]) => void;
48
+ shapes: DrawShape[]; // user shapes
49
+ autoShapes: DrawShape[]; // computer shapes
50
+ current?: DrawCurrent;
51
+ brushes: DrawBrushes;
52
+ prevSvgHash: string;
53
+ }
54
+
55
+ export interface DrawCurrent {
56
+ orig: cg.Key; // orig key of drawing
57
+ dest?: cg.Key; // shape dest, or undefined for circle
58
+ mouseSq?: cg.Key; // square being moused over
59
+ pos: cg.NumberPair; // relative current position
60
+ brush: cg.BrushColor; // brush name for shape
61
+ snapToValidMove: boolean; // whether to snap to valid piece moves
62
+ }
63
+
64
+ const brushes: cg.BrushColor[] = ['green', 'red', 'blue', 'yellow'];
65
+
66
+ export function start(state: State, e: cg.MouchEvent): void {
67
+ // support one finger touch only
68
+ if (e.touches && e.touches.length > 1) return;
69
+ e.stopPropagation();
70
+ e.preventDefault();
71
+ e.ctrlKey ? unselect(state) : cancelMove(state);
72
+ const pos = eventPosition(e)!,
73
+ orig = getKeyAtDomPos(pos, whitePov(state), state.dom.bounds());
74
+ if (!orig) return;
75
+ state.drawable.current = {
76
+ orig,
77
+ pos,
78
+ brush: eventBrush(e),
79
+ snapToValidMove: state.drawable.defaultSnapToValidMove,
80
+ };
81
+
82
+ processDraw(state);
83
+ }
84
+
85
+ export function processDraw(state: State): void {
86
+ requestAnimationFrame(() => {
87
+ const cur = state.drawable.current;
88
+ if (cur) {
89
+ const keyAtDomPos = getKeyAtDomPos(cur.pos, whitePov(state), state.dom.bounds());
90
+ if (!keyAtDomPos) {
91
+ cur.snapToValidMove = false;
92
+ }
93
+ const mouseSq = cur.snapToValidMove
94
+ ? getSnappedKeyAtDomPos(cur.orig, cur.pos, whitePov(state), state.dom.bounds())
95
+ : keyAtDomPos;
96
+ if (mouseSq !== cur.mouseSq) {
97
+ cur.mouseSq = mouseSq;
98
+ cur.dest = mouseSq !== cur.orig ? mouseSq : undefined;
99
+ state.dom.redrawNow();
100
+ }
101
+ processDraw(state);
102
+ }
103
+ });
104
+ }
105
+
106
+ export function move(state: State, e: cg.MouchEvent): void {
107
+ if (state.drawable.current) state.drawable.current.pos = eventPosition(e)!;
108
+ }
109
+
110
+ export function end(state: State): void {
111
+ const cur = state.drawable.current;
112
+ if (cur) {
113
+ if (cur.mouseSq) addShape(state.drawable, cur);
114
+ cancel(state);
115
+ }
116
+ }
117
+
118
+ export function cancel(state: State): void {
119
+ if (state.drawable.current) {
120
+ state.drawable.current = undefined;
121
+ state.dom.redraw();
122
+ }
123
+ }
124
+
125
+ export function clear(state: State): void {
126
+ if (state.drawable.shapes.length) {
127
+ state.drawable.shapes = [];
128
+ state.dom.redraw();
129
+ onChange(state.drawable);
130
+ }
131
+ }
132
+
133
+ function eventBrush(e: cg.MouchEvent): cg.BrushColor {
134
+ const modA = (e.shiftKey || e.ctrlKey) && isRightButton(e);
135
+ const modB = e.altKey || e.metaKey || e.getModifierState?.('AltGraph');
136
+ return brushes[(modA ? 1 : 0) + (modB ? 2 : 0)];
137
+ }
138
+
139
+ function addShape(drawable: Drawable, cur: DrawCurrent): void {
140
+ const sameShape = (s: DrawShape) => s.orig === cur.orig && s.dest === cur.dest;
141
+ const similar = drawable.shapes.find(sameShape);
142
+ if (similar) drawable.shapes = drawable.shapes.filter(s => !sameShape(s));
143
+ if (!similar || similar.brush !== cur.brush)
144
+ drawable.shapes.push({
145
+ orig: cur.orig,
146
+ dest: cur.dest,
147
+ brush: cur.brush,
148
+ });
149
+ onChange(drawable);
150
+ }
151
+
152
+ function onChange(drawable: Drawable): void {
153
+ if (drawable.onChange) drawable.onChange(drawable.shapes);
154
+ }
package/src/drop.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { State } from './state.js';
2
+ import * as cg from './types.js';
3
+ import * as board from './board.js';
4
+ import * as util from './util.js';
5
+ import { cancel as dragCancel } from './drag.js';
6
+
7
+ export function setDropMode(s: State, piece?: cg.Piece): void {
8
+ s.dropmode = {
9
+ active: true,
10
+ piece,
11
+ };
12
+ dragCancel(s);
13
+ }
14
+
15
+ export function cancelDropMode(s: State): void {
16
+ s.dropmode = {
17
+ active: false,
18
+ };
19
+ }
20
+
21
+ export function drop(s: State, e: cg.MouchEvent): void {
22
+ if (!s.dropmode.active) return;
23
+
24
+ board.unsetPremove(s);
25
+ board.unsetPredrop(s);
26
+
27
+ const piece = s.dropmode.piece;
28
+
29
+ if (piece) {
30
+ s.pieces.set('a0', piece);
31
+ const position = util.eventPosition(e);
32
+ const dest = position && board.getKeyAtDomPos(position, board.whitePov(s), s.dom.bounds());
33
+ if (dest) board.dropNewPiece(s, 'a0', dest);
34
+ }
35
+ s.dom.redraw();
36
+ }
package/src/events.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { State } from './state.js';
2
+ import * as drag from './drag.js';
3
+ import * as draw from './draw.js';
4
+ import { drop } from './drop.js';
5
+ import { isRightButton } from './util.js';
6
+ import * as cg from './types.js';
7
+
8
+ type MouchBind = (e: cg.MouchEvent) => void;
9
+ type StateMouchBind = (d: State, e: cg.MouchEvent) => void;
10
+
11
+ export function bindBoard(s: State, onResize: () => void): void {
12
+ const boardEl = s.dom.elements.board;
13
+
14
+ if ('ResizeObserver' in window) new ResizeObserver(onResize).observe(s.dom.elements.wrap);
15
+
16
+ if (s.disableContextMenu || s.drawable.enabled) {
17
+ boardEl.addEventListener('contextmenu', e => e.preventDefault());
18
+ }
19
+
20
+ if (s.viewOnly) return;
21
+
22
+ // Cannot be passive, because we prevent touch scrolling and dragging of
23
+ // selected elements.
24
+ const onStart = startDragOrDraw(s);
25
+ boardEl.addEventListener('touchstart', onStart as EventListener, {
26
+ passive: false,
27
+ });
28
+ boardEl.addEventListener('mousedown', onStart as EventListener, {
29
+ passive: false,
30
+ });
31
+ }
32
+
33
+ // returns the unbind function
34
+ export function bindDocument(s: State, onResize: () => void): cg.Unbind {
35
+ const unbinds: cg.Unbind[] = [];
36
+
37
+ // Old versions of Edge and Safari do not support ResizeObserver. Send
38
+ // chessground.resize if a user action has changed the bounds of the board.
39
+ if (!('ResizeObserver' in window)) unbinds.push(unbindable(document.body, 'chessground.resize', onResize));
40
+
41
+ if (!s.viewOnly) {
42
+ const onmove = dragOrDraw(s, drag.move, draw.move);
43
+ const onend = dragOrDraw(s, drag.end, draw.end);
44
+
45
+ for (const ev of ['touchmove', 'mousemove'])
46
+ unbinds.push(unbindable(document, ev, onmove as EventListener));
47
+ for (const ev of ['touchend', 'mouseup']) unbinds.push(unbindable(document, ev, onend as EventListener));
48
+
49
+ const onScroll = () => s.dom.bounds.clear();
50
+ unbinds.push(unbindable(document, 'scroll', onScroll, { capture: true, passive: true }));
51
+ unbinds.push(unbindable(window, 'resize', onScroll, { passive: true }));
52
+ }
53
+
54
+ return () => unbinds.forEach(f => f());
55
+ }
56
+
57
+ function unbindable(
58
+ el: EventTarget,
59
+ eventName: string,
60
+ callback: EventListener,
61
+ options?: AddEventListenerOptions,
62
+ ): cg.Unbind {
63
+ el.addEventListener(eventName, callback, options);
64
+ return () => el.removeEventListener(eventName, callback, options);
65
+ }
66
+
67
+ const startDragOrDraw =
68
+ (s: State): MouchBind =>
69
+ e => {
70
+ if (s.draggable.current) drag.cancel(s);
71
+ else if (s.drawable.current) draw.cancel(s);
72
+ else if (e.shiftKey || isRightButton(e)) {
73
+ if (s.drawable.enabled) draw.start(s, e);
74
+ } else if (!s.viewOnly) {
75
+ if (s.dropmode.active) drop(s, e);
76
+ else drag.start(s, e);
77
+ }
78
+ };
79
+
80
+ const dragOrDraw =
81
+ (s: State, withDrag: StateMouchBind, withDraw: StateMouchBind): MouchBind =>
82
+ e => {
83
+ if (s.drawable.current) {
84
+ if (s.drawable.enabled) withDraw(s, e);
85
+ } else if (!s.viewOnly) withDrag(s, e);
86
+ };
@@ -0,0 +1,19 @@
1
+ import { State } from './state.js';
2
+ import { Key } from './types.js';
3
+
4
+ export function explosion(state: State, keys: Key[]): void {
5
+ state.exploding = { stage: 1, keys };
6
+ state.dom.redraw();
7
+ setTimeout(() => {
8
+ setStage(state, 2);
9
+ setTimeout(() => setStage(state, undefined), 120);
10
+ }, 120);
11
+ }
12
+
13
+ function setStage(state: State, stage: number | undefined): void {
14
+ if (state.exploding) {
15
+ if (stage) state.exploding.stage = stage;
16
+ else state.exploding = undefined;
17
+ state.dom.redraw();
18
+ }
19
+ }