gigaboard 1.4.2 → 1.5.1
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/dist/{board-model-BMtkLQvL.d.cts → board-model-BIVxSCWC.d.cts} +73 -2
- package/dist/{board-model-BMtkLQvL.d.ts → board-model-BIVxSCWC.d.ts} +73 -2
- package/dist/{chunk-EJUGH25P.js → chunk-53XWDREK.js} +4 -3
- package/dist/chunk-53XWDREK.js.map +1 -0
- package/dist/{chunk-SO6AOEFF.js → chunk-6F3FZAG7.js} +116 -18
- package/dist/chunk-6F3FZAG7.js.map +1 -0
- package/dist/{chunk-UBD62NOI.cjs → chunk-FUP2LDMO.cjs} +121 -16
- package/dist/chunk-FUP2LDMO.cjs.map +1 -0
- package/dist/{chunk-D45G3J5E.cjs → chunk-N4WTPXFC.cjs} +4 -2
- package/dist/chunk-N4WTPXFC.cjs.map +1 -0
- package/dist/core.cjs +72 -44
- package/dist/core.d.cts +17 -12
- package/dist/core.d.ts +17 -12
- package/dist/core.js +1 -1
- package/dist/{gigachess-C08uAeFw.d.ts → gigachess-DN-ncmAU.d.ts} +1 -1
- package/dist/{gigachess-Dr5gCN68.d.cts → gigachess-XpNGPM2m.d.cts} +1 -1
- package/dist/index.cjs +911 -250
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -8
- package/dist/index.d.ts +61 -8
- package/dist/index.js +818 -189
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +11 -11
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{types-CcOgT5ov.d.ts → types-4W8ULiE2.d.ts} +1 -1
- package/dist/{types-DfqRXtbD.d.cts → types-Dox7oLof.d.cts} +1 -1
- package/package.json +2 -2
- package/dist/chunk-D45G3J5E.cjs.map +0 -1
- package/dist/chunk-EJUGH25P.js.map +0 -1
- package/dist/chunk-SO6AOEFF.js.map +0 -1
- package/dist/chunk-UBD62NOI.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,71 @@
|
|
|
1
|
-
import { PieceType, Color, createDragController, createGigachessAdapter, createBoardModel, isEmptyCell, pieceTypeOf, colorOf, encodeBoardCell, decodePackedMove } from './chunk-
|
|
2
|
-
export { Color, PieceType, createBoardModel, createGigachessAdapter, packMove, preloadGigachessAdapter, unpackMove } from './chunk-
|
|
3
|
-
import { CSS_VARS, defaultTheme, defaultPieces,
|
|
4
|
-
export { CSS_VARS, DEFAULT_ILLEGAL_FLASH_TINT, DEFAULT_PREMOVE_TINT, defaultArrowColors, defaultPieces, defaultTheme } from './chunk-
|
|
1
|
+
import { PieceType, Color, createDragController, createGigachessAdapter, createBoardModel, BOARD_CELL_BK, BOARD_CELL_BQ, BOARD_CELL_BR, BOARD_CELL_BB, BOARD_CELL_BN, BOARD_CELL_BP, BOARD_CELL_WK, BOARD_CELL_WQ, BOARD_CELL_WR, BOARD_CELL_WB, BOARD_CELL_WN, BOARD_CELL_WP, isEmptyCell, pieceTypeOf, colorOf, encodeBoardCell, isNullMove, decodePackedMove, isNullMoveUci } from './chunk-6F3FZAG7.js';
|
|
2
|
+
export { Color, NULL_MOVE_SAN, NULL_MOVE_SANS, NULL_MOVE_UCI, NULL_MOVE_WORD, PieceType, createBoardModel, createGigachessAdapter, isNullMove, isNullMoveSan, isNullMoveUci, packMove, preloadGigachessAdapter, unpackMove } from './chunk-6F3FZAG7.js';
|
|
3
|
+
import { CSS_VARS, parseFenPlacement, STARTING_FEN, defaultTheme, defaultPieces, defaultArrowColors, DEFAULT_ILLEGAL_FLASH_TINT, DEFAULT_PREMOVE_TINT } from './chunk-53XWDREK.js';
|
|
4
|
+
export { CSS_VARS, DEFAULT_ILLEGAL_FLASH_TINT, DEFAULT_PREMOVE_TINT, defaultArrowColors, defaultPieces, defaultTheme } from './chunk-53XWDREK.js';
|
|
5
5
|
import './chunk-OQLY5GJF.js';
|
|
6
6
|
import './chunk-U6WAD322.js';
|
|
7
7
|
import './chunk-44YKPXXT.js';
|
|
8
8
|
import { memo, forwardRef, useRef, useMemo, useImperativeHandle, useLayoutEffect, useEffect, useCallback, useSyncExternalStore, useState } from 'react';
|
|
9
|
-
import { jsxs, Fragment
|
|
9
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
10
|
|
|
11
|
+
var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
|
|
12
|
+
function Coordinates({ orientation, ranksPosition = "left" }) {
|
|
13
|
+
const ranksOnRight = ranksPosition === "right";
|
|
14
|
+
return /* @__PURE__ */ jsxs(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
"aria-hidden": "true",
|
|
18
|
+
style: {
|
|
19
|
+
position: "absolute",
|
|
20
|
+
inset: 0,
|
|
21
|
+
pointerEvents: "none",
|
|
22
|
+
fontSize: "min(1.6vmin, 11px)",
|
|
23
|
+
fontWeight: 600,
|
|
24
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
25
|
+
},
|
|
26
|
+
children: [
|
|
27
|
+
FILE_LETTERS.map((letter, idx) => {
|
|
28
|
+
const col = orientation === "white" ? idx : 7 - idx;
|
|
29
|
+
const bottomRank = orientation === "white" ? 0 : 7;
|
|
30
|
+
const isLightSquare = (col + bottomRank) % 2 === 1;
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"span",
|
|
33
|
+
{
|
|
34
|
+
style: {
|
|
35
|
+
position: "absolute",
|
|
36
|
+
left: `${col * 12.5 + 10}%`,
|
|
37
|
+
bottom: "1%",
|
|
38
|
+
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
39
|
+
},
|
|
40
|
+
children: letter
|
|
41
|
+
},
|
|
42
|
+
`file-${letter}`
|
|
43
|
+
);
|
|
44
|
+
}),
|
|
45
|
+
FILE_LETTERS.map((_, idx) => {
|
|
46
|
+
const rank = idx;
|
|
47
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
48
|
+
const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
|
|
49
|
+
const isLightSquare = (edgeFile + rank) % 2 === 1;
|
|
50
|
+
const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
|
|
51
|
+
return /* @__PURE__ */ jsx(
|
|
52
|
+
"span",
|
|
53
|
+
{
|
|
54
|
+
style: {
|
|
55
|
+
position: "absolute",
|
|
56
|
+
top: `${row * 12.5 + 1}%`,
|
|
57
|
+
...sideStyle,
|
|
58
|
+
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
59
|
+
},
|
|
60
|
+
children: rank + 1
|
|
61
|
+
},
|
|
62
|
+
`rank-${rank}`
|
|
63
|
+
);
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
11
69
|
function useBoardSnapshot(model) {
|
|
12
70
|
return useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot);
|
|
13
71
|
}
|
|
@@ -21,12 +79,738 @@ function useSquareCell(model, index) {
|
|
|
21
79
|
},
|
|
22
80
|
[model, index]
|
|
23
81
|
);
|
|
24
|
-
const getSnapshot = useCallback(() => {
|
|
25
|
-
if (!model) return 0;
|
|
26
|
-
const snap = model.getSnapshot();
|
|
27
|
-
return snap.board[index] ?? 0;
|
|
28
|
-
}, [model, index]);
|
|
29
|
-
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
82
|
+
const getSnapshot = useCallback(() => {
|
|
83
|
+
if (!model) return 0;
|
|
84
|
+
const snap = model.getSnapshot();
|
|
85
|
+
return snap.board[index] ?? 0;
|
|
86
|
+
}, [model, index]);
|
|
87
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
88
|
+
}
|
|
89
|
+
function gridCoord(index, orientation) {
|
|
90
|
+
const file = index & 7;
|
|
91
|
+
const rank = index >> 3;
|
|
92
|
+
const col = orientation === "white" ? file + 1 : 8 - file;
|
|
93
|
+
const row = orientation === "white" ? 8 - rank : rank + 1;
|
|
94
|
+
return { col, row };
|
|
95
|
+
}
|
|
96
|
+
function algebraicOf(index) {
|
|
97
|
+
const file = index & 7;
|
|
98
|
+
const rank = index >> 3;
|
|
99
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
100
|
+
}
|
|
101
|
+
var SLOT_BASE_STYLE = Object.freeze({
|
|
102
|
+
display: "flex",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
justifyContent: "center",
|
|
105
|
+
pointerEvents: "none",
|
|
106
|
+
containerType: "size"
|
|
107
|
+
});
|
|
108
|
+
function PieceSlotImpl({ index, model, orientation, pieces }) {
|
|
109
|
+
const cell = useSquareCell(model, index);
|
|
110
|
+
if (cell === 0) return null;
|
|
111
|
+
const { col, row } = gridCoord(index, orientation);
|
|
112
|
+
return /* @__PURE__ */ jsx(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
"data-piece-square": algebraicOf(index),
|
|
116
|
+
"data-piece-cell": cell,
|
|
117
|
+
style: {
|
|
118
|
+
...SLOT_BASE_STYLE,
|
|
119
|
+
gridColumnStart: col,
|
|
120
|
+
gridRowStart: row,
|
|
121
|
+
filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
|
|
122
|
+
},
|
|
123
|
+
children: pieces({ cell, square: index })
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
var PieceSlot = memo(PieceSlotImpl);
|
|
128
|
+
PieceSlot.displayName = "PieceSlot";
|
|
129
|
+
var CONTAINER_STYLE = Object.freeze({
|
|
130
|
+
position: "absolute",
|
|
131
|
+
inset: 0,
|
|
132
|
+
display: "grid",
|
|
133
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
134
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
135
|
+
pointerEvents: "none"
|
|
136
|
+
});
|
|
137
|
+
var PieceLayer = memo(function PieceLayer2({
|
|
138
|
+
model,
|
|
139
|
+
orientation,
|
|
140
|
+
pieces
|
|
141
|
+
}) {
|
|
142
|
+
const slots = [];
|
|
143
|
+
for (let i = 0; i < 64; i++) {
|
|
144
|
+
slots.push(
|
|
145
|
+
/* @__PURE__ */ jsx(
|
|
146
|
+
PieceSlot,
|
|
147
|
+
{
|
|
148
|
+
index: i,
|
|
149
|
+
model,
|
|
150
|
+
orientation,
|
|
151
|
+
pieces
|
|
152
|
+
},
|
|
153
|
+
i
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// src/lib/geometry.ts
|
|
161
|
+
function getSquareAtPoint(container, clientX, clientY, orientation) {
|
|
162
|
+
const rect = container.getBoundingClientRect();
|
|
163
|
+
const relX = clientX - rect.left;
|
|
164
|
+
const relY = clientY - rect.top;
|
|
165
|
+
if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
|
|
166
|
+
const col = Math.floor(relX / rect.width * 8);
|
|
167
|
+
const row = Math.floor(relY / rect.height * 8);
|
|
168
|
+
const file = orientation === "white" ? col : 7 - col;
|
|
169
|
+
const rank = orientation === "white" ? 7 - row : row;
|
|
170
|
+
return rank * 8 + file;
|
|
171
|
+
}
|
|
172
|
+
function getPointAtSquareCentre(container, square, orientation) {
|
|
173
|
+
const rect = container.getBoundingClientRect();
|
|
174
|
+
const sqW = rect.width / 8;
|
|
175
|
+
const sqH = rect.height / 8;
|
|
176
|
+
const file = square & 7;
|
|
177
|
+
const rank = square >> 3;
|
|
178
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
179
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
180
|
+
return {
|
|
181
|
+
x: rect.left + sqW * col + sqW / 2,
|
|
182
|
+
y: rect.top + sqH * row + sqH / 2
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function algebraicOf2(square) {
|
|
186
|
+
const file = square & 7;
|
|
187
|
+
const rank = square >> 3;
|
|
188
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
189
|
+
}
|
|
190
|
+
function squareOf(algebraic) {
|
|
191
|
+
if (algebraic.length !== 2) return null;
|
|
192
|
+
const file = algebraic.charCodeAt(0) - 97;
|
|
193
|
+
const rank = algebraic.charCodeAt(1) - 49;
|
|
194
|
+
if (file < 0 || file > 7 || rank < 0 || rank > 7) return null;
|
|
195
|
+
return rank << 3 | file;
|
|
196
|
+
}
|
|
197
|
+
var PIECE_CHARS = ["", "P", "N", "B", "R", "Q", "K", "p", "n", "b", "r", "q", "k"];
|
|
198
|
+
function formatFenPlacement(board) {
|
|
199
|
+
const rows = [];
|
|
200
|
+
for (let r = 7; r >= 0; r--) {
|
|
201
|
+
let empty = 0;
|
|
202
|
+
let row = "";
|
|
203
|
+
for (let f = 0; f < 8; f++) {
|
|
204
|
+
const cell = board[r * 8 + f] ?? 0;
|
|
205
|
+
if (cell === 0) {
|
|
206
|
+
empty++;
|
|
207
|
+
} else {
|
|
208
|
+
if (empty > 0) {
|
|
209
|
+
row += String(empty);
|
|
210
|
+
empty = 0;
|
|
211
|
+
}
|
|
212
|
+
row += PIECE_CHARS[cell] ?? "";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (empty > 0) {
|
|
216
|
+
row += String(empty);
|
|
217
|
+
}
|
|
218
|
+
rows.push(row);
|
|
219
|
+
}
|
|
220
|
+
return rows.join("/");
|
|
221
|
+
}
|
|
222
|
+
function updateFenPlacement(originalFen, newPlacement) {
|
|
223
|
+
const tokens = originalFen.trim().split(/\s+/);
|
|
224
|
+
if (tokens.length <= 1) {
|
|
225
|
+
return newPlacement;
|
|
226
|
+
}
|
|
227
|
+
tokens[0] = newPlacement;
|
|
228
|
+
return tokens.join(" ");
|
|
229
|
+
}
|
|
230
|
+
function pieceNameFromCell(cell) {
|
|
231
|
+
if (cell === 0) return "empty";
|
|
232
|
+
const color = cell > 6 ? "black" : "white";
|
|
233
|
+
const type = (cell - 1) % 6;
|
|
234
|
+
const names = ["pawn", "knight", "bishop", "rook", "queen", "king"];
|
|
235
|
+
return `${color} ${names[type] ?? "piece"}`;
|
|
236
|
+
}
|
|
237
|
+
function defaultSquareLabel(square, cell) {
|
|
238
|
+
const sq = algebraicOf2(square);
|
|
239
|
+
if (cell === 0) {
|
|
240
|
+
return `${sq}, empty`;
|
|
241
|
+
}
|
|
242
|
+
return `${sq}, ${pieceNameFromCell(cell)}`;
|
|
243
|
+
}
|
|
244
|
+
function cellFromKey(key) {
|
|
245
|
+
switch (key) {
|
|
246
|
+
case "P":
|
|
247
|
+
return BOARD_CELL_WP;
|
|
248
|
+
case "N":
|
|
249
|
+
return BOARD_CELL_WN;
|
|
250
|
+
case "B":
|
|
251
|
+
return BOARD_CELL_WB;
|
|
252
|
+
case "R":
|
|
253
|
+
return BOARD_CELL_WR;
|
|
254
|
+
case "Q":
|
|
255
|
+
return BOARD_CELL_WQ;
|
|
256
|
+
case "K":
|
|
257
|
+
return BOARD_CELL_WK;
|
|
258
|
+
case "p":
|
|
259
|
+
return BOARD_CELL_BP;
|
|
260
|
+
case "n":
|
|
261
|
+
return BOARD_CELL_BN;
|
|
262
|
+
case "b":
|
|
263
|
+
return BOARD_CELL_BB;
|
|
264
|
+
case "r":
|
|
265
|
+
return BOARD_CELL_BR;
|
|
266
|
+
case "q":
|
|
267
|
+
return BOARD_CELL_BQ;
|
|
268
|
+
case "k":
|
|
269
|
+
return BOARD_CELL_BK;
|
|
270
|
+
default:
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function cellFromPieceString(piece) {
|
|
275
|
+
if (!piece || piece === "erase") return 0;
|
|
276
|
+
if (piece.length === 2) {
|
|
277
|
+
const c0 = piece[0];
|
|
278
|
+
const c1 = piece[1];
|
|
279
|
+
if (c0 && c1) {
|
|
280
|
+
const color = c0.toLowerCase();
|
|
281
|
+
const type = c1.toUpperCase();
|
|
282
|
+
if (color === "w") return cellFromKey(type);
|
|
283
|
+
if (color === "b") return cellFromKey(type.toLowerCase());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return cellFromKey(piece);
|
|
287
|
+
}
|
|
288
|
+
function getOppositePieceString(piece) {
|
|
289
|
+
if (!piece || piece === "erase") return null;
|
|
290
|
+
if (piece.length === 2) {
|
|
291
|
+
const c0 = piece[0];
|
|
292
|
+
const c1 = piece[1];
|
|
293
|
+
if (c0 && c1) {
|
|
294
|
+
const color = c0 === "w" ? "b" : "w";
|
|
295
|
+
return `${color}${c1}`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return piece === piece.toUpperCase() ? piece.toLowerCase() : piece.toUpperCase();
|
|
299
|
+
}
|
|
300
|
+
function moveSquare(current, direction, orientation) {
|
|
301
|
+
const file = current & 7;
|
|
302
|
+
const rank = current >> 3;
|
|
303
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
304
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
305
|
+
let newCol = col;
|
|
306
|
+
let newRow = row;
|
|
307
|
+
switch (direction) {
|
|
308
|
+
case "up":
|
|
309
|
+
newRow = Math.max(0, row - 1);
|
|
310
|
+
break;
|
|
311
|
+
case "down":
|
|
312
|
+
newRow = Math.min(7, row + 1);
|
|
313
|
+
break;
|
|
314
|
+
case "left":
|
|
315
|
+
newCol = Math.max(0, col - 1);
|
|
316
|
+
break;
|
|
317
|
+
case "right":
|
|
318
|
+
newCol = Math.min(7, col + 1);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
const newFile = orientation === "white" ? newCol : 7 - newCol;
|
|
322
|
+
const newRank = orientation === "white" ? 7 - newRow : newRow;
|
|
323
|
+
return newRank * 8 + newFile & 63;
|
|
324
|
+
}
|
|
325
|
+
function rankEndSquare(current, which, orientation) {
|
|
326
|
+
const rank = current >> 3;
|
|
327
|
+
if (orientation === "white") {
|
|
328
|
+
return rank << 3 | (which === "home" ? 0 : 7);
|
|
329
|
+
}
|
|
330
|
+
return rank << 3 | (which === "home" ? 7 : 0);
|
|
331
|
+
}
|
|
332
|
+
function BoardEditor({
|
|
333
|
+
fen = "8/8/8/8/8/8/8/8 w - - 0 1",
|
|
334
|
+
onChange,
|
|
335
|
+
onPieceChange,
|
|
336
|
+
selectedPiece,
|
|
337
|
+
onSelectPiece,
|
|
338
|
+
orientation = "white",
|
|
339
|
+
theme = defaultTheme,
|
|
340
|
+
pieces = defaultPieces,
|
|
341
|
+
showCoordinates = false,
|
|
342
|
+
style,
|
|
343
|
+
className,
|
|
344
|
+
ariaLabel = "Board editor",
|
|
345
|
+
onAnnounce,
|
|
346
|
+
getSquareAriaLabel,
|
|
347
|
+
focusedSquare: propFocusedSquare,
|
|
348
|
+
onSquareFocus,
|
|
349
|
+
id,
|
|
350
|
+
onClear,
|
|
351
|
+
onSetStartingPosition,
|
|
352
|
+
onToggleSideToMove,
|
|
353
|
+
onFocus,
|
|
354
|
+
onBlur
|
|
355
|
+
}) {
|
|
356
|
+
const containerRef = useRef(null);
|
|
357
|
+
const liveRegionRef = useRef(null);
|
|
358
|
+
const liveCounter = useRef(0);
|
|
359
|
+
const [board, setBoard] = useState(() => {
|
|
360
|
+
try {
|
|
361
|
+
return parseFenPlacement(fen);
|
|
362
|
+
} catch {
|
|
363
|
+
return new Uint8Array(64);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
const [internalFocusedSquare, setInternalFocusedSquare] = useState(
|
|
367
|
+
() => orientation === "white" ? 28 : 36
|
|
368
|
+
// e4 or e5
|
|
369
|
+
);
|
|
370
|
+
const focusedSquare = propFocusedSquare ?? internalFocusedSquare;
|
|
371
|
+
const setFocusedSquare = setInternalFocusedSquare;
|
|
372
|
+
const [hasKeyboardFocus, setHasKeyboardFocus] = useState(false);
|
|
373
|
+
const [dragging, setDragging] = useState(null);
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
try {
|
|
376
|
+
const nextBoard = parseFenPlacement(fen);
|
|
377
|
+
setBoard(nextBoard);
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}, [fen]);
|
|
381
|
+
const announce = useCallback(
|
|
382
|
+
(message) => {
|
|
383
|
+
onAnnounce?.(message);
|
|
384
|
+
const region = liveRegionRef.current;
|
|
385
|
+
if (region) {
|
|
386
|
+
liveCounter.current++;
|
|
387
|
+
region.setAttribute("data-live-id", String(liveCounter.current));
|
|
388
|
+
region.textContent = "";
|
|
389
|
+
void region.offsetHeight;
|
|
390
|
+
region.textContent = message;
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
[onAnnounce]
|
|
394
|
+
);
|
|
395
|
+
const getSquareDescription = useCallback(
|
|
396
|
+
(square, cell) => {
|
|
397
|
+
const sq = algebraicOf2(square);
|
|
398
|
+
if (getSquareAriaLabel) {
|
|
399
|
+
return getSquareAriaLabel(square, cell, sq);
|
|
400
|
+
}
|
|
401
|
+
return defaultSquareLabel(square, cell);
|
|
402
|
+
},
|
|
403
|
+
[getSquareAriaLabel]
|
|
404
|
+
);
|
|
405
|
+
const announceSquare = useCallback(
|
|
406
|
+
(square) => {
|
|
407
|
+
const cell = board[square] ?? 0;
|
|
408
|
+
const desc = getSquareDescription(square, cell);
|
|
409
|
+
announce(desc);
|
|
410
|
+
onSquareFocus?.(square, cell, algebraicOf2(square));
|
|
411
|
+
},
|
|
412
|
+
[announce, board, getSquareDescription, onSquareFocus]
|
|
413
|
+
);
|
|
414
|
+
const commitBoard = useCallback(
|
|
415
|
+
(nextBoard) => {
|
|
416
|
+
setBoard(nextBoard);
|
|
417
|
+
const newPlacement = formatFenPlacement(nextBoard);
|
|
418
|
+
const updatedFen = updateFenPlacement(fen, newPlacement);
|
|
419
|
+
onChange?.(updatedFen);
|
|
420
|
+
},
|
|
421
|
+
[fen, onChange]
|
|
422
|
+
);
|
|
423
|
+
const placePieceAt = useCallback(
|
|
424
|
+
(square, cellOrCode) => {
|
|
425
|
+
const targetCell = typeof cellOrCode === "string" ? cellFromPieceString(cellOrCode) : cellOrCode;
|
|
426
|
+
const currentCell = board[square] ?? 0;
|
|
427
|
+
const nextBoard = new Uint8Array(board);
|
|
428
|
+
if (targetCell !== 0 && currentCell === targetCell) {
|
|
429
|
+
nextBoard[square] = 0;
|
|
430
|
+
commitBoard(nextBoard);
|
|
431
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
432
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
433
|
+
} else if (targetCell !== 0) {
|
|
434
|
+
nextBoard[square] = targetCell;
|
|
435
|
+
commitBoard(nextBoard);
|
|
436
|
+
onPieceChange?.(square, targetCell, algebraicOf2(square));
|
|
437
|
+
const name = pieceNameFromCell(targetCell);
|
|
438
|
+
announce(`${name} placed on ${algebraicOf2(square)}`);
|
|
439
|
+
const char = PIECE_CHARS[targetCell] ?? "";
|
|
440
|
+
const code = targetCell <= 6 ? `w${char}` : `b${char.toUpperCase()}`;
|
|
441
|
+
onSelectPiece?.(code);
|
|
442
|
+
} else {
|
|
443
|
+
nextBoard[square] = 0;
|
|
444
|
+
commitBoard(nextBoard);
|
|
445
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
446
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
[announce, board, commitBoard, onPieceChange, onSelectPiece]
|
|
450
|
+
);
|
|
451
|
+
const removePieceAt = useCallback(
|
|
452
|
+
(square) => {
|
|
453
|
+
const current = board[square] ?? 0;
|
|
454
|
+
if (current === 0) {
|
|
455
|
+
announce(`${algebraicOf2(square)} is empty`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const nextBoard = new Uint8Array(board);
|
|
459
|
+
nextBoard[square] = 0;
|
|
460
|
+
commitBoard(nextBoard);
|
|
461
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
462
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
463
|
+
},
|
|
464
|
+
[announce, board, commitBoard, onPieceChange]
|
|
465
|
+
);
|
|
466
|
+
const handleKeyDown = useCallback(
|
|
467
|
+
(e) => {
|
|
468
|
+
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
469
|
+
if (e.key.startsWith("Arrow")) {
|
|
470
|
+
if (e.shiftKey) return;
|
|
471
|
+
e.preventDefault();
|
|
472
|
+
const dir = e.key === "ArrowUp" ? "up" : e.key === "ArrowDown" ? "down" : e.key === "ArrowLeft" ? "left" : "right";
|
|
473
|
+
const next = moveSquare(focusedSquare, dir, orientation);
|
|
474
|
+
setFocusedSquare(next);
|
|
475
|
+
announceSquare(next);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (e.key === "Home" || e.key === "End") {
|
|
479
|
+
e.preventDefault();
|
|
480
|
+
const next = rankEndSquare(focusedSquare, e.key === "Home" ? "home" : "end", orientation);
|
|
481
|
+
setFocusedSquare(next);
|
|
482
|
+
announceSquare(next);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (e.key === "Backspace" || e.key === "Delete" || e.key === "x" || e.key === "X") {
|
|
486
|
+
e.preventDefault();
|
|
487
|
+
removePieceAt(focusedSquare);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (e.key === "c" || e.key === "C") {
|
|
491
|
+
e.preventDefault();
|
|
492
|
+
if (onClear) {
|
|
493
|
+
onClear();
|
|
494
|
+
} else {
|
|
495
|
+
commitBoard(new Uint8Array(64));
|
|
496
|
+
announce("Board cleared");
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (e.key === "s" || e.key === "S") {
|
|
501
|
+
e.preventDefault();
|
|
502
|
+
if (onSetStartingPosition) {
|
|
503
|
+
onSetStartingPosition();
|
|
504
|
+
} else {
|
|
505
|
+
commitBoard(parseFenPlacement(STARTING_FEN));
|
|
506
|
+
announce("Starting position");
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (e.key === "t" || e.key === "T") {
|
|
511
|
+
e.preventDefault();
|
|
512
|
+
onToggleSideToMove?.();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
516
|
+
e.preventDefault();
|
|
517
|
+
if (selectedPiece) {
|
|
518
|
+
const piece = e.shiftKey ? getOppositePieceString(selectedPiece) : selectedPiece;
|
|
519
|
+
if (piece) placePieceAt(focusedSquare, piece);
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const pieceCell = cellFromKey(e.key);
|
|
524
|
+
if (pieceCell !== 0) {
|
|
525
|
+
e.preventDefault();
|
|
526
|
+
placePieceAt(focusedSquare, pieceCell);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
[
|
|
530
|
+
announce,
|
|
531
|
+
announceSquare,
|
|
532
|
+
commitBoard,
|
|
533
|
+
focusedSquare,
|
|
534
|
+
onClear,
|
|
535
|
+
onSetStartingPosition,
|
|
536
|
+
onToggleSideToMove,
|
|
537
|
+
orientation,
|
|
538
|
+
placePieceAt,
|
|
539
|
+
removePieceAt,
|
|
540
|
+
selectedPiece
|
|
541
|
+
]
|
|
542
|
+
);
|
|
543
|
+
const handlePointerDown = (e) => {
|
|
544
|
+
if (e.button !== 0) return;
|
|
545
|
+
const container = containerRef.current;
|
|
546
|
+
if (!container) return;
|
|
547
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
548
|
+
if (sq === null) return;
|
|
549
|
+
container.focus();
|
|
550
|
+
setFocusedSquare(sq);
|
|
551
|
+
onSquareFocus?.(sq, board[sq] ?? 0, algebraicOf2(sq));
|
|
552
|
+
const cell = board[sq] ?? 0;
|
|
553
|
+
if (cell === 0) {
|
|
554
|
+
if (selectedPiece) {
|
|
555
|
+
placePieceAt(sq, selectedPiece);
|
|
556
|
+
}
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
560
|
+
setDragging({
|
|
561
|
+
origin: sq,
|
|
562
|
+
cell,
|
|
563
|
+
x: e.clientX,
|
|
564
|
+
y: e.clientY
|
|
565
|
+
});
|
|
566
|
+
};
|
|
567
|
+
const handlePointerMove = (e) => {
|
|
568
|
+
if (!dragging) return;
|
|
569
|
+
setDragging((prev) => prev ? { ...prev, x: e.clientX, y: e.clientY } : null);
|
|
570
|
+
};
|
|
571
|
+
const handlePointerUp = (e) => {
|
|
572
|
+
if (!dragging) return;
|
|
573
|
+
const container = containerRef.current;
|
|
574
|
+
const originSq = dragging.origin;
|
|
575
|
+
const movedCell = dragging.cell;
|
|
576
|
+
setDragging(null);
|
|
577
|
+
if (!container) return;
|
|
578
|
+
const targetSq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
579
|
+
if (targetSq === null) {
|
|
580
|
+
removePieceAt(originSq);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (targetSq === originSq) {
|
|
584
|
+
if (selectedPiece) {
|
|
585
|
+
placePieceAt(originSq, selectedPiece);
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const nextBoard = new Uint8Array(board);
|
|
590
|
+
nextBoard[originSq] = 0;
|
|
591
|
+
nextBoard[targetSq] = movedCell;
|
|
592
|
+
commitBoard(nextBoard);
|
|
593
|
+
announce(`${pieceNameFromCell(movedCell)} moved to ${algebraicOf2(targetSq)}`);
|
|
594
|
+
setFocusedSquare(targetSq);
|
|
595
|
+
};
|
|
596
|
+
const handleContextMenu = (e) => {
|
|
597
|
+
e.preventDefault();
|
|
598
|
+
const container = containerRef.current;
|
|
599
|
+
if (!container) return;
|
|
600
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
601
|
+
if (sq === null) return;
|
|
602
|
+
setFocusedSquare(sq);
|
|
603
|
+
if (selectedPiece) {
|
|
604
|
+
const opp = getOppositePieceString(selectedPiece);
|
|
605
|
+
if (opp) placePieceAt(sq, opp);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const squareOrder = useMemo(() => {
|
|
609
|
+
const squares = [];
|
|
610
|
+
if (orientation === "white") {
|
|
611
|
+
for (let rank = 7; rank >= 0; rank--) {
|
|
612
|
+
for (let file = 0; file < 8; file++) {
|
|
613
|
+
squares.push(rank * 8 + file);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
for (let rank = 0; rank < 8; rank++) {
|
|
618
|
+
for (let file = 7; file >= 0; file--) {
|
|
619
|
+
squares.push(rank * 8 + file);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return squares;
|
|
624
|
+
}, [orientation]);
|
|
625
|
+
const containerStyle = {
|
|
626
|
+
position: "relative",
|
|
627
|
+
aspectRatio: "1 / 1",
|
|
628
|
+
width: "100%",
|
|
629
|
+
touchAction: "none",
|
|
630
|
+
outline: hasKeyboardFocus ? "3px solid var(--gb-editor-focus, #d97706)" : "none",
|
|
631
|
+
outlineOffset: "2px",
|
|
632
|
+
borderRadius: "8px",
|
|
633
|
+
...theme,
|
|
634
|
+
...style
|
|
635
|
+
};
|
|
636
|
+
return /* @__PURE__ */ jsxs(
|
|
637
|
+
"div",
|
|
638
|
+
{
|
|
639
|
+
ref: containerRef,
|
|
640
|
+
id,
|
|
641
|
+
role: "application",
|
|
642
|
+
tabIndex: 0,
|
|
643
|
+
"aria-label": ariaLabel,
|
|
644
|
+
"aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Enter Space Backspace Delete p P n N b B r R q Q k K c C s S t T",
|
|
645
|
+
onKeyDown: handleKeyDown,
|
|
646
|
+
onFocus: (e) => {
|
|
647
|
+
if (e.target === e.currentTarget) {
|
|
648
|
+
setHasKeyboardFocus(true);
|
|
649
|
+
announceSquare(focusedSquare);
|
|
650
|
+
}
|
|
651
|
+
onFocus?.(e);
|
|
652
|
+
},
|
|
653
|
+
onBlur: (e) => {
|
|
654
|
+
if (e.target === e.currentTarget) {
|
|
655
|
+
setHasKeyboardFocus(false);
|
|
656
|
+
}
|
|
657
|
+
onBlur?.(e);
|
|
658
|
+
},
|
|
659
|
+
onPointerDown: handlePointerDown,
|
|
660
|
+
onPointerMove: handlePointerMove,
|
|
661
|
+
onPointerUp: handlePointerUp,
|
|
662
|
+
onPointerCancel: () => setDragging(null),
|
|
663
|
+
onContextMenu: handleContextMenu,
|
|
664
|
+
className,
|
|
665
|
+
style: containerStyle,
|
|
666
|
+
"data-gb-editor": "true",
|
|
667
|
+
"data-gb-orientation": orientation,
|
|
668
|
+
children: [
|
|
669
|
+
/* @__PURE__ */ jsx(
|
|
670
|
+
"div",
|
|
671
|
+
{
|
|
672
|
+
"aria-hidden": "true",
|
|
673
|
+
style: {
|
|
674
|
+
position: "absolute",
|
|
675
|
+
inset: 0,
|
|
676
|
+
display: "grid",
|
|
677
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
678
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
679
|
+
pointerEvents: "none"
|
|
680
|
+
},
|
|
681
|
+
children: squareOrder.map((sq) => {
|
|
682
|
+
const file = sq & 7;
|
|
683
|
+
const rank = sq >> 3;
|
|
684
|
+
const isLight = (file + rank & 1) !== 0;
|
|
685
|
+
const isFocused = hasKeyboardFocus && focusedSquare === sq;
|
|
686
|
+
return /* @__PURE__ */ jsx(
|
|
687
|
+
"div",
|
|
688
|
+
{
|
|
689
|
+
"data-square": algebraicOf2(sq),
|
|
690
|
+
style: {
|
|
691
|
+
position: "relative",
|
|
692
|
+
backgroundColor: `var(${isLight ? CSS_VARS.SQ_LIGHT : CSS_VARS.SQ_DARK})`,
|
|
693
|
+
backgroundImage: `var(${isLight ? CSS_VARS.SQ_LIGHT_IMAGE : CSS_VARS.SQ_DARK_IMAGE},none)`,
|
|
694
|
+
backgroundSize: `var(${CSS_VARS.SQ_IMAGE_SIZE},cover)`,
|
|
695
|
+
boxShadow: isFocused ? "inset 0 0 0 3px rgba(255, 206, 76, 0.95)" : void 0,
|
|
696
|
+
userSelect: "none"
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
sq
|
|
700
|
+
);
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
),
|
|
704
|
+
/* @__PURE__ */ jsx(
|
|
705
|
+
"div",
|
|
706
|
+
{
|
|
707
|
+
"aria-hidden": "true",
|
|
708
|
+
style: {
|
|
709
|
+
position: "absolute",
|
|
710
|
+
inset: 0,
|
|
711
|
+
display: "grid",
|
|
712
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
713
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
714
|
+
pointerEvents: "none"
|
|
715
|
+
},
|
|
716
|
+
children: squareOrder.map((sq) => {
|
|
717
|
+
const cell = board[sq] ?? 0;
|
|
718
|
+
if (cell === 0) return null;
|
|
719
|
+
const isOrigin = dragging?.origin === sq;
|
|
720
|
+
const { col, row } = gridCoord(sq, orientation);
|
|
721
|
+
return /* @__PURE__ */ jsx(
|
|
722
|
+
"div",
|
|
723
|
+
{
|
|
724
|
+
"data-piece-square": algebraicOf2(sq),
|
|
725
|
+
style: {
|
|
726
|
+
...SLOT_BASE_STYLE,
|
|
727
|
+
gridColumnStart: col,
|
|
728
|
+
gridRowStart: row,
|
|
729
|
+
opacity: isOrigin ? 0.25 : 1,
|
|
730
|
+
filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`,
|
|
731
|
+
pointerEvents: "none"
|
|
732
|
+
},
|
|
733
|
+
children: pieces({ cell, square: sq })
|
|
734
|
+
},
|
|
735
|
+
sq
|
|
736
|
+
);
|
|
737
|
+
})
|
|
738
|
+
}
|
|
739
|
+
),
|
|
740
|
+
hasKeyboardFocus && /* @__PURE__ */ jsx(
|
|
741
|
+
"div",
|
|
742
|
+
{
|
|
743
|
+
"aria-hidden": "true",
|
|
744
|
+
style: {
|
|
745
|
+
position: "absolute",
|
|
746
|
+
inset: 0,
|
|
747
|
+
display: "grid",
|
|
748
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
749
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
750
|
+
pointerEvents: "none",
|
|
751
|
+
zIndex: 10
|
|
752
|
+
},
|
|
753
|
+
children: (() => {
|
|
754
|
+
const { col, row } = gridCoord(focusedSquare, orientation);
|
|
755
|
+
return /* @__PURE__ */ jsx(
|
|
756
|
+
"div",
|
|
757
|
+
{
|
|
758
|
+
"data-gb-editor-focus-square": algebraicOf2(focusedSquare),
|
|
759
|
+
style: {
|
|
760
|
+
gridColumnStart: col,
|
|
761
|
+
gridRowStart: row,
|
|
762
|
+
outline: "3px solid var(--gb-editor-sq-focus, #f59e0b)",
|
|
763
|
+
outlineOffset: "-3px",
|
|
764
|
+
boxShadow: "inset 0 0 0 1.5px rgba(0, 0, 0, 0.75), 0 0 8px rgba(245, 158, 11, 0.7)",
|
|
765
|
+
borderRadius: "2px",
|
|
766
|
+
pointerEvents: "none"
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
})()
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
dragging && containerRef.current ? /* @__PURE__ */ jsx(
|
|
774
|
+
"div",
|
|
775
|
+
{
|
|
776
|
+
"aria-hidden": "true",
|
|
777
|
+
style: {
|
|
778
|
+
position: "fixed",
|
|
779
|
+
left: dragging.x - 30,
|
|
780
|
+
top: dragging.y - 30,
|
|
781
|
+
width: 60,
|
|
782
|
+
height: 60,
|
|
783
|
+
pointerEvents: "none",
|
|
784
|
+
zIndex: 100,
|
|
785
|
+
filter: `var(${dragging.cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
|
|
786
|
+
},
|
|
787
|
+
children: pieces({ cell: dragging.cell, square: dragging.origin })
|
|
788
|
+
}
|
|
789
|
+
) : null,
|
|
790
|
+
showCoordinates ? /* @__PURE__ */ jsx(Coordinates, { orientation }) : null,
|
|
791
|
+
/* @__PURE__ */ jsx(
|
|
792
|
+
"div",
|
|
793
|
+
{
|
|
794
|
+
ref: liveRegionRef,
|
|
795
|
+
role: "status",
|
|
796
|
+
"aria-live": "assertive",
|
|
797
|
+
"aria-atomic": "true",
|
|
798
|
+
style: {
|
|
799
|
+
position: "absolute",
|
|
800
|
+
width: 1,
|
|
801
|
+
height: 1,
|
|
802
|
+
padding: 0,
|
|
803
|
+
margin: -1,
|
|
804
|
+
overflow: "hidden",
|
|
805
|
+
clip: "rect(0, 0, 0, 0)",
|
|
806
|
+
whiteSpace: "nowrap",
|
|
807
|
+
border: 0
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
)
|
|
811
|
+
]
|
|
812
|
+
}
|
|
813
|
+
);
|
|
30
814
|
}
|
|
31
815
|
function squareCentrePct(index, orientation) {
|
|
32
816
|
const file = index & 7;
|
|
@@ -607,7 +1391,7 @@ function injectFocusRingStyles() {
|
|
|
607
1391
|
`;
|
|
608
1392
|
document.head.appendChild(style);
|
|
609
1393
|
}
|
|
610
|
-
function
|
|
1394
|
+
function algebraicOf3(index) {
|
|
611
1395
|
const file = index & 7;
|
|
612
1396
|
const rank = index >> 3;
|
|
613
1397
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -626,7 +1410,7 @@ function SquareImpl({
|
|
|
626
1410
|
const rank = index >> 3;
|
|
627
1411
|
const isLight = (file + rank) % 2 === 1;
|
|
628
1412
|
const cell = useSquareCell(model, index);
|
|
629
|
-
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) :
|
|
1413
|
+
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf3(index);
|
|
630
1414
|
const internalRef = useRef(null);
|
|
631
1415
|
const lastReportedFocusRef = useRef(false);
|
|
632
1416
|
const setRef = useCallback(
|
|
@@ -669,7 +1453,7 @@ function SquareImpl({
|
|
|
669
1453
|
"aria-rowindex": rank + 1,
|
|
670
1454
|
"aria-colindex": file + 1,
|
|
671
1455
|
tabIndex: isFocused ? 0 : -1,
|
|
672
|
-
"data-square":
|
|
1456
|
+
"data-square": algebraicOf3(index),
|
|
673
1457
|
"data-gb-square": "",
|
|
674
1458
|
"data-light": isLight ? "true" : "false",
|
|
675
1459
|
onClick: handleClick,
|
|
@@ -807,64 +1591,6 @@ function CheckLayerInner({ model, orientation }) {
|
|
|
807
1591
|
}
|
|
808
1592
|
);
|
|
809
1593
|
}
|
|
810
|
-
var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
|
|
811
|
-
function Coordinates({ orientation, ranksPosition = "left" }) {
|
|
812
|
-
const ranksOnRight = ranksPosition === "right";
|
|
813
|
-
return /* @__PURE__ */ jsxs(
|
|
814
|
-
"div",
|
|
815
|
-
{
|
|
816
|
-
"aria-hidden": "true",
|
|
817
|
-
style: {
|
|
818
|
-
position: "absolute",
|
|
819
|
-
inset: 0,
|
|
820
|
-
pointerEvents: "none",
|
|
821
|
-
fontSize: "min(1.6vmin, 11px)",
|
|
822
|
-
fontWeight: 600,
|
|
823
|
-
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
824
|
-
},
|
|
825
|
-
children: [
|
|
826
|
-
FILE_LETTERS.map((letter, idx) => {
|
|
827
|
-
const col = orientation === "white" ? idx : 7 - idx;
|
|
828
|
-
const bottomRank = orientation === "white" ? 0 : 7;
|
|
829
|
-
const isLightSquare = (col + bottomRank) % 2 === 1;
|
|
830
|
-
return /* @__PURE__ */ jsx(
|
|
831
|
-
"span",
|
|
832
|
-
{
|
|
833
|
-
style: {
|
|
834
|
-
position: "absolute",
|
|
835
|
-
left: `${col * 12.5 + 10}%`,
|
|
836
|
-
bottom: "1%",
|
|
837
|
-
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
838
|
-
},
|
|
839
|
-
children: letter
|
|
840
|
-
},
|
|
841
|
-
`file-${letter}`
|
|
842
|
-
);
|
|
843
|
-
}),
|
|
844
|
-
FILE_LETTERS.map((_, idx) => {
|
|
845
|
-
const rank = idx;
|
|
846
|
-
const row = orientation === "white" ? 7 - rank : rank;
|
|
847
|
-
const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
|
|
848
|
-
const isLightSquare = (edgeFile + rank) % 2 === 1;
|
|
849
|
-
const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
|
|
850
|
-
return /* @__PURE__ */ jsx(
|
|
851
|
-
"span",
|
|
852
|
-
{
|
|
853
|
-
style: {
|
|
854
|
-
position: "absolute",
|
|
855
|
-
top: `${row * 12.5 + 1}%`,
|
|
856
|
-
...sideStyle,
|
|
857
|
-
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
858
|
-
},
|
|
859
|
-
children: rank + 1
|
|
860
|
-
},
|
|
861
|
-
`rank-${rank}`
|
|
862
|
-
);
|
|
863
|
-
})
|
|
864
|
-
]
|
|
865
|
-
}
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
1594
|
var WRAPPER_STYLE = Object.freeze({
|
|
869
1595
|
position: "absolute",
|
|
870
1596
|
top: 0,
|
|
@@ -1007,7 +1733,7 @@ function IllegalFlashLayer({ at, orientation }) {
|
|
|
1007
1733
|
}
|
|
1008
1734
|
);
|
|
1009
1735
|
}
|
|
1010
|
-
function
|
|
1736
|
+
function algebraicOf4(index) {
|
|
1011
1737
|
const file = index & 7;
|
|
1012
1738
|
const rank = index >> 3;
|
|
1013
1739
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1035,14 +1761,15 @@ function pieceName(cell) {
|
|
|
1035
1761
|
}
|
|
1036
1762
|
function announcementFor(lastMove, board, wasCapture) {
|
|
1037
1763
|
const { from, to, kind, promotion } = decodePackedMove(lastMove);
|
|
1764
|
+
if (kind === "pass") return "Passes.";
|
|
1038
1765
|
if (kind === "castle") {
|
|
1039
1766
|
const isKingside = (to & 7) > (from & 7);
|
|
1040
1767
|
return isKingside ? "Castles kingside." : "Castles queenside.";
|
|
1041
1768
|
}
|
|
1042
1769
|
const landed = board[to] ?? 0;
|
|
1043
1770
|
const name = pieceName(landed);
|
|
1044
|
-
const fromSq =
|
|
1045
|
-
const toSq =
|
|
1771
|
+
const fromSq = algebraicOf4(from);
|
|
1772
|
+
const toSq = algebraicOf4(to);
|
|
1046
1773
|
if (kind === "promotion" && promotion !== null) {
|
|
1047
1774
|
const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
|
|
1048
1775
|
const action = wasCapture ? "captures and promotes" : "promotes";
|
|
@@ -1091,76 +1818,6 @@ function LiveRegion({ model }) {
|
|
|
1091
1818
|
}
|
|
1092
1819
|
);
|
|
1093
1820
|
}
|
|
1094
|
-
function gridCoord(index, orientation) {
|
|
1095
|
-
const file = index & 7;
|
|
1096
|
-
const rank = index >> 3;
|
|
1097
|
-
const col = orientation === "white" ? file + 1 : 8 - file;
|
|
1098
|
-
const row = orientation === "white" ? 8 - rank : rank + 1;
|
|
1099
|
-
return { col, row };
|
|
1100
|
-
}
|
|
1101
|
-
function algebraicOf3(index) {
|
|
1102
|
-
const file = index & 7;
|
|
1103
|
-
const rank = index >> 3;
|
|
1104
|
-
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1105
|
-
}
|
|
1106
|
-
var SLOT_BASE_STYLE = Object.freeze({
|
|
1107
|
-
display: "flex",
|
|
1108
|
-
alignItems: "center",
|
|
1109
|
-
justifyContent: "center",
|
|
1110
|
-
pointerEvents: "none",
|
|
1111
|
-
containerType: "size"
|
|
1112
|
-
});
|
|
1113
|
-
function PieceSlotImpl({ index, model, orientation, pieces }) {
|
|
1114
|
-
const cell = useSquareCell(model, index);
|
|
1115
|
-
if (cell === 0) return null;
|
|
1116
|
-
const { col, row } = gridCoord(index, orientation);
|
|
1117
|
-
return /* @__PURE__ */ jsx(
|
|
1118
|
-
"div",
|
|
1119
|
-
{
|
|
1120
|
-
"data-piece-square": algebraicOf3(index),
|
|
1121
|
-
"data-piece-cell": cell,
|
|
1122
|
-
style: {
|
|
1123
|
-
...SLOT_BASE_STYLE,
|
|
1124
|
-
gridColumnStart: col,
|
|
1125
|
-
gridRowStart: row,
|
|
1126
|
-
filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
|
|
1127
|
-
},
|
|
1128
|
-
children: pieces({ cell, square: index })
|
|
1129
|
-
}
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
var PieceSlot = memo(PieceSlotImpl);
|
|
1133
|
-
PieceSlot.displayName = "PieceSlot";
|
|
1134
|
-
var CONTAINER_STYLE = Object.freeze({
|
|
1135
|
-
position: "absolute",
|
|
1136
|
-
inset: 0,
|
|
1137
|
-
display: "grid",
|
|
1138
|
-
gridTemplateColumns: "repeat(8, 1fr)",
|
|
1139
|
-
gridTemplateRows: "repeat(8, 1fr)",
|
|
1140
|
-
pointerEvents: "none"
|
|
1141
|
-
});
|
|
1142
|
-
var PieceLayer = memo(function PieceLayer2({
|
|
1143
|
-
model,
|
|
1144
|
-
orientation,
|
|
1145
|
-
pieces
|
|
1146
|
-
}) {
|
|
1147
|
-
const slots = [];
|
|
1148
|
-
for (let i = 0; i < 64; i++) {
|
|
1149
|
-
slots.push(
|
|
1150
|
-
/* @__PURE__ */ jsx(
|
|
1151
|
-
PieceSlot,
|
|
1152
|
-
{
|
|
1153
|
-
index: i,
|
|
1154
|
-
model,
|
|
1155
|
-
orientation,
|
|
1156
|
-
pieces
|
|
1157
|
-
},
|
|
1158
|
-
i
|
|
1159
|
-
)
|
|
1160
|
-
);
|
|
1161
|
-
}
|
|
1162
|
-
return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
|
|
1163
|
-
});
|
|
1164
1821
|
function positionOf3(index, orientation) {
|
|
1165
1822
|
const file = index & 7;
|
|
1166
1823
|
const rank = index >> 3;
|
|
@@ -1372,7 +2029,7 @@ function PromotionOverlay({
|
|
|
1372
2029
|
{
|
|
1373
2030
|
role: "dialog",
|
|
1374
2031
|
"aria-modal": "true",
|
|
1375
|
-
"aria-label": `Choose promotion piece at ${
|
|
2032
|
+
"aria-label": `Choose promotion piece at ${algebraicOf5(to)}`,
|
|
1376
2033
|
onClick: (e) => e.stopPropagation(),
|
|
1377
2034
|
style: {
|
|
1378
2035
|
position: "absolute",
|
|
@@ -1452,7 +2109,7 @@ function pieceName2(t) {
|
|
|
1452
2109
|
return "Piece";
|
|
1453
2110
|
}
|
|
1454
2111
|
}
|
|
1455
|
-
function
|
|
2112
|
+
function algebraicOf5(index) {
|
|
1456
2113
|
const file = index & 7;
|
|
1457
2114
|
const rank = index >> 3;
|
|
1458
2115
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1468,7 +2125,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1468
2125
|
/* @__PURE__ */ jsx(
|
|
1469
2126
|
"div",
|
|
1470
2127
|
{
|
|
1471
|
-
"data-piece-square":
|
|
2128
|
+
"data-piece-square": algebraicOf(index),
|
|
1472
2129
|
"data-piece-cell": cell,
|
|
1473
2130
|
style: {
|
|
1474
2131
|
...SLOT_BASE_STYLE,
|
|
@@ -1486,7 +2143,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1486
2143
|
}
|
|
1487
2144
|
var DEFAULT_DURATION_MS = 60;
|
|
1488
2145
|
var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
1489
|
-
function
|
|
2146
|
+
function algebraicOf6(index) {
|
|
1490
2147
|
const file = index & 7;
|
|
1491
2148
|
const rank = index >> 3;
|
|
1492
2149
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1534,7 +2191,7 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1534
2191
|
case "en-passant":
|
|
1535
2192
|
case "promotion": {
|
|
1536
2193
|
const toEl = container.querySelector(
|
|
1537
|
-
`[data-piece-square="${
|
|
2194
|
+
`[data-piece-square="${algebraicOf6(d.to)}"]`
|
|
1538
2195
|
);
|
|
1539
2196
|
if (toEl === null) break;
|
|
1540
2197
|
const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
|
|
@@ -1543,10 +2200,10 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1543
2200
|
}
|
|
1544
2201
|
case "castle": {
|
|
1545
2202
|
const kingEl = container.querySelector(
|
|
1546
|
-
`[data-piece-square="${
|
|
2203
|
+
`[data-piece-square="${algebraicOf6(d.kingTo)}"]`
|
|
1547
2204
|
);
|
|
1548
2205
|
const rookEl = container.querySelector(
|
|
1549
|
-
`[data-piece-square="${
|
|
2206
|
+
`[data-piece-square="${algebraicOf6(d.rookTo)}"]`
|
|
1550
2207
|
);
|
|
1551
2208
|
if (kingEl !== null) {
|
|
1552
2209
|
const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
|
|
@@ -1590,39 +2247,6 @@ function AnimationRunner({
|
|
|
1590
2247
|
}, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
|
|
1591
2248
|
return null;
|
|
1592
2249
|
}
|
|
1593
|
-
|
|
1594
|
-
// src/lib/geometry.ts
|
|
1595
|
-
function getSquareAtPoint(container, clientX, clientY, orientation) {
|
|
1596
|
-
const rect = container.getBoundingClientRect();
|
|
1597
|
-
const relX = clientX - rect.left;
|
|
1598
|
-
const relY = clientY - rect.top;
|
|
1599
|
-
if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
|
|
1600
|
-
const col = Math.floor(relX / rect.width * 8);
|
|
1601
|
-
const row = Math.floor(relY / rect.height * 8);
|
|
1602
|
-
const file = orientation === "white" ? col : 7 - col;
|
|
1603
|
-
const rank = orientation === "white" ? 7 - row : row;
|
|
1604
|
-
return rank * 8 + file;
|
|
1605
|
-
}
|
|
1606
|
-
function getPointAtSquareCentre(container, square, orientation) {
|
|
1607
|
-
const rect = container.getBoundingClientRect();
|
|
1608
|
-
const sqW = rect.width / 8;
|
|
1609
|
-
const sqH = rect.height / 8;
|
|
1610
|
-
const file = square & 7;
|
|
1611
|
-
const rank = square >> 3;
|
|
1612
|
-
const col = orientation === "white" ? file : 7 - file;
|
|
1613
|
-
const row = orientation === "white" ? 7 - rank : rank;
|
|
1614
|
-
return {
|
|
1615
|
-
x: rect.left + sqW * col + sqW / 2,
|
|
1616
|
-
y: rect.top + sqH * row + sqH / 2
|
|
1617
|
-
};
|
|
1618
|
-
}
|
|
1619
|
-
function algebraicOf6(square) {
|
|
1620
|
-
const file = square & 7;
|
|
1621
|
-
const rank = square >> 3;
|
|
1622
|
-
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
// src/hooks/use-arrow-gesture.ts
|
|
1626
2250
|
function colorForModifiers(e, palette) {
|
|
1627
2251
|
if (e.shiftKey) return palette.shift;
|
|
1628
2252
|
if (e.altKey) return palette.alt;
|
|
@@ -1937,7 +2561,7 @@ function useDrag(options) {
|
|
|
1937
2561
|
controller.cancel();
|
|
1938
2562
|
return;
|
|
1939
2563
|
}
|
|
1940
|
-
const originLabel =
|
|
2564
|
+
const originLabel = algebraicOf2(evt.from);
|
|
1941
2565
|
originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
|
|
1942
2566
|
if (originEl !== null) {
|
|
1943
2567
|
originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
|
|
@@ -2186,7 +2810,7 @@ function useLastMoveController(model, squareRefs, enabled) {
|
|
|
2186
2810
|
const lastMove = snap.lastMove;
|
|
2187
2811
|
let from = null;
|
|
2188
2812
|
let to = null;
|
|
2189
|
-
if (lastMove !== null) {
|
|
2813
|
+
if (lastMove !== null && !isNullMove(lastMove)) {
|
|
2190
2814
|
from = lastMove & 63;
|
|
2191
2815
|
to = lastMove >> 6 & 63;
|
|
2192
2816
|
}
|
|
@@ -2596,6 +3220,9 @@ var ILLEGAL_FLASH_HOLD_MS = 320;
|
|
|
2596
3220
|
var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
|
|
2597
3221
|
var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
2598
3222
|
var UCI_TRANSITION_RE = /^([a-h][1-8])([a-h][1-8])([nbrq])?$/i;
|
|
3223
|
+
function isNullTransition(transition) {
|
|
3224
|
+
return isNullMoveUci(transition.uci);
|
|
3225
|
+
}
|
|
2599
3226
|
function prefersReducedMotion3() {
|
|
2600
3227
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2601
3228
|
return false;
|
|
@@ -2643,6 +3270,7 @@ function castleRookTransition(from, to) {
|
|
|
2643
3270
|
};
|
|
2644
3271
|
}
|
|
2645
3272
|
function transitionSquares(transition) {
|
|
3273
|
+
if (isNullTransition(transition)) return [];
|
|
2646
3274
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2647
3275
|
if (parsed === null) return [];
|
|
2648
3276
|
const moveFrom = parsed[1] ?? "";
|
|
@@ -2653,6 +3281,7 @@ function transitionSquares(transition) {
|
|
|
2653
3281
|
return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
|
|
2654
3282
|
}
|
|
2655
3283
|
function parseTransitionMove(transition) {
|
|
3284
|
+
if (isNullTransition(transition)) return null;
|
|
2656
3285
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2657
3286
|
if (parsed === null) return null;
|
|
2658
3287
|
const from = parsed[1];
|
|
@@ -3223,8 +3852,8 @@ function useChessGame(options = {}) {
|
|
|
3223
3852
|
}
|
|
3224
3853
|
|
|
3225
3854
|
// src/index.ts
|
|
3226
|
-
var PACKAGE_VERSION = "1.
|
|
3855
|
+
var PACKAGE_VERSION = "1.5.0";
|
|
3227
3856
|
|
|
3228
|
-
export { AnimationRunner, Chessboard, DEFAULT_MOVE_HAPTIC_PATTERNS, DEFAULT_MOVE_SOUND_SOURCES, PACKAGE_VERSION, classifyMoveFeedback, getPointAtSquareCentre, getSquareAtPoint, triggerHaptic, triggerMoveHaptic, useArrowGesture, useBoardSlice, useBoardSnapshot, useChessGame, useClickToMove, useDrag, useHoverSquare, useKeyboardNav, useMoveHaptics, useMoveSound, useSquareCell, warmupHaptics };
|
|
3857
|
+
export { AnimationRunner, BoardEditor, Chessboard, DEFAULT_MOVE_HAPTIC_PATTERNS, DEFAULT_MOVE_SOUND_SOURCES, PACKAGE_VERSION, algebraicOf2 as algebraicOf, classifyMoveFeedback, formatFenPlacement, getPointAtSquareCentre, getSquareAtPoint, squareOf, triggerHaptic, triggerMoveHaptic, useArrowGesture, useBoardSlice, useBoardSnapshot, useChessGame, useClickToMove, useDrag, useHoverSquare, useKeyboardNav, useMoveHaptics, useMoveSound, useSquareCell, warmupHaptics };
|
|
3229
3858
|
//# sourceMappingURL=index.js.map
|
|
3230
3859
|
//# sourceMappingURL=index.js.map
|