gigaboard 1.4.2 → 1.5.0
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 +868 -250
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -8
- package/dist/index.d.ts +55 -8
- package/dist/index.js +775 -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,695 @@ 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
|
+
onClear,
|
|
350
|
+
onSetStartingPosition,
|
|
351
|
+
onToggleSideToMove
|
|
352
|
+
}) {
|
|
353
|
+
const containerRef = useRef(null);
|
|
354
|
+
const liveRegionRef = useRef(null);
|
|
355
|
+
const liveCounter = useRef(0);
|
|
356
|
+
const [board, setBoard] = useState(() => {
|
|
357
|
+
try {
|
|
358
|
+
return parseFenPlacement(fen);
|
|
359
|
+
} catch {
|
|
360
|
+
return new Uint8Array(64);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
const [internalFocusedSquare, setInternalFocusedSquare] = useState(
|
|
364
|
+
() => orientation === "white" ? 28 : 36
|
|
365
|
+
// e4 or e5
|
|
366
|
+
);
|
|
367
|
+
const focusedSquare = propFocusedSquare ?? internalFocusedSquare;
|
|
368
|
+
const setFocusedSquare = setInternalFocusedSquare;
|
|
369
|
+
const [hasKeyboardFocus, setHasKeyboardFocus] = useState(false);
|
|
370
|
+
const [dragging, setDragging] = useState(null);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
try {
|
|
373
|
+
const nextBoard = parseFenPlacement(fen);
|
|
374
|
+
setBoard(nextBoard);
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
}, [fen]);
|
|
378
|
+
const announce = useCallback(
|
|
379
|
+
(message) => {
|
|
380
|
+
onAnnounce?.(message);
|
|
381
|
+
const region = liveRegionRef.current;
|
|
382
|
+
if (region) {
|
|
383
|
+
liveCounter.current++;
|
|
384
|
+
region.setAttribute("data-live-id", String(liveCounter.current));
|
|
385
|
+
region.textContent = "";
|
|
386
|
+
void region.offsetHeight;
|
|
387
|
+
region.textContent = message;
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
[onAnnounce]
|
|
391
|
+
);
|
|
392
|
+
const getSquareDescription = useCallback(
|
|
393
|
+
(square, cell) => {
|
|
394
|
+
const sq = algebraicOf2(square);
|
|
395
|
+
if (getSquareAriaLabel) {
|
|
396
|
+
return getSquareAriaLabel(square, cell, sq);
|
|
397
|
+
}
|
|
398
|
+
return defaultSquareLabel(square, cell);
|
|
399
|
+
},
|
|
400
|
+
[getSquareAriaLabel]
|
|
401
|
+
);
|
|
402
|
+
const announceSquare = useCallback(
|
|
403
|
+
(square) => {
|
|
404
|
+
const cell = board[square] ?? 0;
|
|
405
|
+
const desc = getSquareDescription(square, cell);
|
|
406
|
+
announce(desc);
|
|
407
|
+
onSquareFocus?.(square, cell, algebraicOf2(square));
|
|
408
|
+
},
|
|
409
|
+
[announce, board, getSquareDescription, onSquareFocus]
|
|
410
|
+
);
|
|
411
|
+
const commitBoard = useCallback(
|
|
412
|
+
(nextBoard) => {
|
|
413
|
+
setBoard(nextBoard);
|
|
414
|
+
const newPlacement = formatFenPlacement(nextBoard);
|
|
415
|
+
const updatedFen = updateFenPlacement(fen, newPlacement);
|
|
416
|
+
onChange?.(updatedFen);
|
|
417
|
+
},
|
|
418
|
+
[fen, onChange]
|
|
419
|
+
);
|
|
420
|
+
const placePieceAt = useCallback(
|
|
421
|
+
(square, cellOrCode) => {
|
|
422
|
+
const targetCell = typeof cellOrCode === "string" ? cellFromPieceString(cellOrCode) : cellOrCode;
|
|
423
|
+
const currentCell = board[square] ?? 0;
|
|
424
|
+
const nextBoard = new Uint8Array(board);
|
|
425
|
+
if (targetCell !== 0 && currentCell === targetCell) {
|
|
426
|
+
nextBoard[square] = 0;
|
|
427
|
+
commitBoard(nextBoard);
|
|
428
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
429
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
430
|
+
} else if (targetCell !== 0) {
|
|
431
|
+
nextBoard[square] = targetCell;
|
|
432
|
+
commitBoard(nextBoard);
|
|
433
|
+
onPieceChange?.(square, targetCell, algebraicOf2(square));
|
|
434
|
+
const name = pieceNameFromCell(targetCell);
|
|
435
|
+
announce(`${name} placed on ${algebraicOf2(square)}`);
|
|
436
|
+
const char = PIECE_CHARS[targetCell] ?? "";
|
|
437
|
+
const code = targetCell <= 6 ? `w${char}` : `b${char.toUpperCase()}`;
|
|
438
|
+
onSelectPiece?.(code);
|
|
439
|
+
} else {
|
|
440
|
+
nextBoard[square] = 0;
|
|
441
|
+
commitBoard(nextBoard);
|
|
442
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
443
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
[announce, board, commitBoard, onPieceChange, onSelectPiece]
|
|
447
|
+
);
|
|
448
|
+
const removePieceAt = useCallback(
|
|
449
|
+
(square) => {
|
|
450
|
+
const current = board[square] ?? 0;
|
|
451
|
+
if (current === 0) {
|
|
452
|
+
announce(`${algebraicOf2(square)} is empty`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const nextBoard = new Uint8Array(board);
|
|
456
|
+
nextBoard[square] = 0;
|
|
457
|
+
commitBoard(nextBoard);
|
|
458
|
+
onPieceChange?.(square, 0, algebraicOf2(square));
|
|
459
|
+
announce(`Piece removed from ${algebraicOf2(square)}`);
|
|
460
|
+
},
|
|
461
|
+
[announce, board, commitBoard, onPieceChange]
|
|
462
|
+
);
|
|
463
|
+
const handleKeyDown = useCallback(
|
|
464
|
+
(e) => {
|
|
465
|
+
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
466
|
+
if (e.key.startsWith("Arrow")) {
|
|
467
|
+
if (e.shiftKey) return;
|
|
468
|
+
e.preventDefault();
|
|
469
|
+
const dir = e.key === "ArrowUp" ? "up" : e.key === "ArrowDown" ? "down" : e.key === "ArrowLeft" ? "left" : "right";
|
|
470
|
+
const next = moveSquare(focusedSquare, dir, orientation);
|
|
471
|
+
setFocusedSquare(next);
|
|
472
|
+
announceSquare(next);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (e.key === "Home" || e.key === "End") {
|
|
476
|
+
e.preventDefault();
|
|
477
|
+
const next = rankEndSquare(focusedSquare, e.key === "Home" ? "home" : "end", orientation);
|
|
478
|
+
setFocusedSquare(next);
|
|
479
|
+
announceSquare(next);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (e.key === "Backspace" || e.key === "Delete" || e.key === "x" || e.key === "X") {
|
|
483
|
+
e.preventDefault();
|
|
484
|
+
removePieceAt(focusedSquare);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (e.key === "c" || e.key === "C") {
|
|
488
|
+
e.preventDefault();
|
|
489
|
+
if (onClear) {
|
|
490
|
+
onClear();
|
|
491
|
+
} else {
|
|
492
|
+
commitBoard(new Uint8Array(64));
|
|
493
|
+
announce("Board cleared");
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (e.key === "s" || e.key === "S") {
|
|
498
|
+
e.preventDefault();
|
|
499
|
+
if (onSetStartingPosition) {
|
|
500
|
+
onSetStartingPosition();
|
|
501
|
+
} else {
|
|
502
|
+
commitBoard(parseFenPlacement(STARTING_FEN));
|
|
503
|
+
announce("Starting position");
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (e.key === "t" || e.key === "T") {
|
|
508
|
+
e.preventDefault();
|
|
509
|
+
onToggleSideToMove?.();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
513
|
+
e.preventDefault();
|
|
514
|
+
if (selectedPiece) {
|
|
515
|
+
const piece = e.shiftKey ? getOppositePieceString(selectedPiece) : selectedPiece;
|
|
516
|
+
if (piece) placePieceAt(focusedSquare, piece);
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const pieceCell = cellFromKey(e.key);
|
|
521
|
+
if (pieceCell !== 0) {
|
|
522
|
+
e.preventDefault();
|
|
523
|
+
placePieceAt(focusedSquare, pieceCell);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
[
|
|
527
|
+
announce,
|
|
528
|
+
announceSquare,
|
|
529
|
+
commitBoard,
|
|
530
|
+
focusedSquare,
|
|
531
|
+
onClear,
|
|
532
|
+
onSetStartingPosition,
|
|
533
|
+
onToggleSideToMove,
|
|
534
|
+
orientation,
|
|
535
|
+
placePieceAt,
|
|
536
|
+
removePieceAt,
|
|
537
|
+
selectedPiece
|
|
538
|
+
]
|
|
539
|
+
);
|
|
540
|
+
const handlePointerDown = (e) => {
|
|
541
|
+
if (e.button !== 0) return;
|
|
542
|
+
const container = containerRef.current;
|
|
543
|
+
if (!container) return;
|
|
544
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
545
|
+
if (sq === null) return;
|
|
546
|
+
container.focus();
|
|
547
|
+
setFocusedSquare(sq);
|
|
548
|
+
onSquareFocus?.(sq, board[sq] ?? 0, algebraicOf2(sq));
|
|
549
|
+
const cell = board[sq] ?? 0;
|
|
550
|
+
if (cell === 0) {
|
|
551
|
+
if (selectedPiece) {
|
|
552
|
+
placePieceAt(sq, selectedPiece);
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
557
|
+
setDragging({
|
|
558
|
+
origin: sq,
|
|
559
|
+
cell,
|
|
560
|
+
x: e.clientX,
|
|
561
|
+
y: e.clientY
|
|
562
|
+
});
|
|
563
|
+
};
|
|
564
|
+
const handlePointerMove = (e) => {
|
|
565
|
+
if (!dragging) return;
|
|
566
|
+
setDragging((prev) => prev ? { ...prev, x: e.clientX, y: e.clientY } : null);
|
|
567
|
+
};
|
|
568
|
+
const handlePointerUp = (e) => {
|
|
569
|
+
if (!dragging) return;
|
|
570
|
+
const container = containerRef.current;
|
|
571
|
+
const originSq = dragging.origin;
|
|
572
|
+
const movedCell = dragging.cell;
|
|
573
|
+
setDragging(null);
|
|
574
|
+
if (!container) return;
|
|
575
|
+
const targetSq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
576
|
+
if (targetSq === null) {
|
|
577
|
+
removePieceAt(originSq);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (targetSq === originSq) {
|
|
581
|
+
if (selectedPiece) {
|
|
582
|
+
placePieceAt(originSq, selectedPiece);
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const nextBoard = new Uint8Array(board);
|
|
587
|
+
nextBoard[originSq] = 0;
|
|
588
|
+
nextBoard[targetSq] = movedCell;
|
|
589
|
+
commitBoard(nextBoard);
|
|
590
|
+
announce(`${pieceNameFromCell(movedCell)} moved to ${algebraicOf2(targetSq)}`);
|
|
591
|
+
setFocusedSquare(targetSq);
|
|
592
|
+
};
|
|
593
|
+
const handleContextMenu = (e) => {
|
|
594
|
+
e.preventDefault();
|
|
595
|
+
const container = containerRef.current;
|
|
596
|
+
if (!container) return;
|
|
597
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
598
|
+
if (sq === null) return;
|
|
599
|
+
setFocusedSquare(sq);
|
|
600
|
+
if (selectedPiece) {
|
|
601
|
+
const opp = getOppositePieceString(selectedPiece);
|
|
602
|
+
if (opp) placePieceAt(sq, opp);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const squareOrder = useMemo(() => {
|
|
606
|
+
const squares = [];
|
|
607
|
+
if (orientation === "white") {
|
|
608
|
+
for (let rank = 7; rank >= 0; rank--) {
|
|
609
|
+
for (let file = 0; file < 8; file++) {
|
|
610
|
+
squares.push(rank * 8 + file);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
for (let rank = 0; rank < 8; rank++) {
|
|
615
|
+
for (let file = 7; file >= 0; file--) {
|
|
616
|
+
squares.push(rank * 8 + file);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return squares;
|
|
621
|
+
}, [orientation]);
|
|
622
|
+
const containerStyle = {
|
|
623
|
+
position: "relative",
|
|
624
|
+
aspectRatio: "1 / 1",
|
|
625
|
+
width: "100%",
|
|
626
|
+
touchAction: "none",
|
|
627
|
+
outline: hasKeyboardFocus ? "3px solid var(--gb-editor-focus, #d97706)" : "none",
|
|
628
|
+
outlineOffset: "2px",
|
|
629
|
+
borderRadius: "8px",
|
|
630
|
+
...theme,
|
|
631
|
+
...style
|
|
632
|
+
};
|
|
633
|
+
return /* @__PURE__ */ jsxs(
|
|
634
|
+
"div",
|
|
635
|
+
{
|
|
636
|
+
ref: containerRef,
|
|
637
|
+
role: "application",
|
|
638
|
+
tabIndex: 0,
|
|
639
|
+
"aria-label": ariaLabel,
|
|
640
|
+
"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",
|
|
641
|
+
onKeyDown: handleKeyDown,
|
|
642
|
+
onFocus: (e) => {
|
|
643
|
+
if (e.target === e.currentTarget) {
|
|
644
|
+
setHasKeyboardFocus(true);
|
|
645
|
+
announceSquare(focusedSquare);
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
onBlur: () => setHasKeyboardFocus(false),
|
|
649
|
+
onPointerDown: handlePointerDown,
|
|
650
|
+
onPointerMove: handlePointerMove,
|
|
651
|
+
onPointerUp: handlePointerUp,
|
|
652
|
+
onPointerCancel: () => setDragging(null),
|
|
653
|
+
onContextMenu: handleContextMenu,
|
|
654
|
+
className,
|
|
655
|
+
style: containerStyle,
|
|
656
|
+
"data-gb-editor": "true",
|
|
657
|
+
"data-gb-orientation": orientation,
|
|
658
|
+
children: [
|
|
659
|
+
/* @__PURE__ */ jsx(
|
|
660
|
+
"div",
|
|
661
|
+
{
|
|
662
|
+
"aria-hidden": "true",
|
|
663
|
+
style: {
|
|
664
|
+
position: "absolute",
|
|
665
|
+
inset: 0,
|
|
666
|
+
display: "grid",
|
|
667
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
668
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
669
|
+
pointerEvents: "none"
|
|
670
|
+
},
|
|
671
|
+
children: squareOrder.map((sq) => {
|
|
672
|
+
const file = sq & 7;
|
|
673
|
+
const rank = sq >> 3;
|
|
674
|
+
const isLight = (file + rank & 1) !== 0;
|
|
675
|
+
const isFocused = hasKeyboardFocus && focusedSquare === sq;
|
|
676
|
+
return /* @__PURE__ */ jsx(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
"data-square": algebraicOf2(sq),
|
|
680
|
+
style: {
|
|
681
|
+
position: "relative",
|
|
682
|
+
backgroundColor: `var(${isLight ? CSS_VARS.SQ_LIGHT : CSS_VARS.SQ_DARK})`,
|
|
683
|
+
backgroundImage: `var(${isLight ? CSS_VARS.SQ_LIGHT_IMAGE : CSS_VARS.SQ_DARK_IMAGE},none)`,
|
|
684
|
+
backgroundSize: `var(${CSS_VARS.SQ_IMAGE_SIZE},cover)`,
|
|
685
|
+
boxShadow: isFocused ? "inset 0 0 0 3px rgba(255, 206, 76, 0.95)" : void 0,
|
|
686
|
+
userSelect: "none"
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
sq
|
|
690
|
+
);
|
|
691
|
+
})
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
/* @__PURE__ */ jsx(
|
|
695
|
+
"div",
|
|
696
|
+
{
|
|
697
|
+
"aria-hidden": "true",
|
|
698
|
+
style: {
|
|
699
|
+
position: "absolute",
|
|
700
|
+
inset: 0,
|
|
701
|
+
display: "grid",
|
|
702
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
703
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
704
|
+
pointerEvents: "none"
|
|
705
|
+
},
|
|
706
|
+
children: squareOrder.map((sq) => {
|
|
707
|
+
const cell = board[sq] ?? 0;
|
|
708
|
+
if (cell === 0) return null;
|
|
709
|
+
const isOrigin = dragging?.origin === sq;
|
|
710
|
+
const { col, row } = gridCoord(sq, orientation);
|
|
711
|
+
return /* @__PURE__ */ jsx(
|
|
712
|
+
"div",
|
|
713
|
+
{
|
|
714
|
+
"data-piece-square": algebraicOf2(sq),
|
|
715
|
+
style: {
|
|
716
|
+
...SLOT_BASE_STYLE,
|
|
717
|
+
gridColumnStart: col,
|
|
718
|
+
gridRowStart: row,
|
|
719
|
+
opacity: isOrigin ? 0.25 : 1,
|
|
720
|
+
filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`,
|
|
721
|
+
pointerEvents: "none"
|
|
722
|
+
},
|
|
723
|
+
children: pieces({ cell, square: sq })
|
|
724
|
+
},
|
|
725
|
+
sq
|
|
726
|
+
);
|
|
727
|
+
})
|
|
728
|
+
}
|
|
729
|
+
),
|
|
730
|
+
dragging && containerRef.current ? /* @__PURE__ */ jsx(
|
|
731
|
+
"div",
|
|
732
|
+
{
|
|
733
|
+
"aria-hidden": "true",
|
|
734
|
+
style: {
|
|
735
|
+
position: "fixed",
|
|
736
|
+
left: dragging.x - 30,
|
|
737
|
+
top: dragging.y - 30,
|
|
738
|
+
width: 60,
|
|
739
|
+
height: 60,
|
|
740
|
+
pointerEvents: "none",
|
|
741
|
+
zIndex: 100,
|
|
742
|
+
filter: `var(${dragging.cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
|
|
743
|
+
},
|
|
744
|
+
children: pieces({ cell: dragging.cell, square: dragging.origin })
|
|
745
|
+
}
|
|
746
|
+
) : null,
|
|
747
|
+
showCoordinates ? /* @__PURE__ */ jsx(Coordinates, { orientation }) : null,
|
|
748
|
+
/* @__PURE__ */ jsx(
|
|
749
|
+
"div",
|
|
750
|
+
{
|
|
751
|
+
ref: liveRegionRef,
|
|
752
|
+
role: "status",
|
|
753
|
+
"aria-live": "assertive",
|
|
754
|
+
"aria-atomic": "true",
|
|
755
|
+
style: {
|
|
756
|
+
position: "absolute",
|
|
757
|
+
width: 1,
|
|
758
|
+
height: 1,
|
|
759
|
+
padding: 0,
|
|
760
|
+
margin: -1,
|
|
761
|
+
overflow: "hidden",
|
|
762
|
+
clip: "rect(0, 0, 0, 0)",
|
|
763
|
+
whiteSpace: "nowrap",
|
|
764
|
+
border: 0
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
)
|
|
768
|
+
]
|
|
769
|
+
}
|
|
770
|
+
);
|
|
30
771
|
}
|
|
31
772
|
function squareCentrePct(index, orientation) {
|
|
32
773
|
const file = index & 7;
|
|
@@ -607,7 +1348,7 @@ function injectFocusRingStyles() {
|
|
|
607
1348
|
`;
|
|
608
1349
|
document.head.appendChild(style);
|
|
609
1350
|
}
|
|
610
|
-
function
|
|
1351
|
+
function algebraicOf3(index) {
|
|
611
1352
|
const file = index & 7;
|
|
612
1353
|
const rank = index >> 3;
|
|
613
1354
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -626,7 +1367,7 @@ function SquareImpl({
|
|
|
626
1367
|
const rank = index >> 3;
|
|
627
1368
|
const isLight = (file + rank) % 2 === 1;
|
|
628
1369
|
const cell = useSquareCell(model, index);
|
|
629
|
-
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) :
|
|
1370
|
+
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf3(index);
|
|
630
1371
|
const internalRef = useRef(null);
|
|
631
1372
|
const lastReportedFocusRef = useRef(false);
|
|
632
1373
|
const setRef = useCallback(
|
|
@@ -669,7 +1410,7 @@ function SquareImpl({
|
|
|
669
1410
|
"aria-rowindex": rank + 1,
|
|
670
1411
|
"aria-colindex": file + 1,
|
|
671
1412
|
tabIndex: isFocused ? 0 : -1,
|
|
672
|
-
"data-square":
|
|
1413
|
+
"data-square": algebraicOf3(index),
|
|
673
1414
|
"data-gb-square": "",
|
|
674
1415
|
"data-light": isLight ? "true" : "false",
|
|
675
1416
|
onClick: handleClick,
|
|
@@ -807,64 +1548,6 @@ function CheckLayerInner({ model, orientation }) {
|
|
|
807
1548
|
}
|
|
808
1549
|
);
|
|
809
1550
|
}
|
|
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
1551
|
var WRAPPER_STYLE = Object.freeze({
|
|
869
1552
|
position: "absolute",
|
|
870
1553
|
top: 0,
|
|
@@ -1007,7 +1690,7 @@ function IllegalFlashLayer({ at, orientation }) {
|
|
|
1007
1690
|
}
|
|
1008
1691
|
);
|
|
1009
1692
|
}
|
|
1010
|
-
function
|
|
1693
|
+
function algebraicOf4(index) {
|
|
1011
1694
|
const file = index & 7;
|
|
1012
1695
|
const rank = index >> 3;
|
|
1013
1696
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1035,14 +1718,15 @@ function pieceName(cell) {
|
|
|
1035
1718
|
}
|
|
1036
1719
|
function announcementFor(lastMove, board, wasCapture) {
|
|
1037
1720
|
const { from, to, kind, promotion } = decodePackedMove(lastMove);
|
|
1721
|
+
if (kind === "pass") return "Passes.";
|
|
1038
1722
|
if (kind === "castle") {
|
|
1039
1723
|
const isKingside = (to & 7) > (from & 7);
|
|
1040
1724
|
return isKingside ? "Castles kingside." : "Castles queenside.";
|
|
1041
1725
|
}
|
|
1042
1726
|
const landed = board[to] ?? 0;
|
|
1043
1727
|
const name = pieceName(landed);
|
|
1044
|
-
const fromSq =
|
|
1045
|
-
const toSq =
|
|
1728
|
+
const fromSq = algebraicOf4(from);
|
|
1729
|
+
const toSq = algebraicOf4(to);
|
|
1046
1730
|
if (kind === "promotion" && promotion !== null) {
|
|
1047
1731
|
const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
|
|
1048
1732
|
const action = wasCapture ? "captures and promotes" : "promotes";
|
|
@@ -1091,76 +1775,6 @@ function LiveRegion({ model }) {
|
|
|
1091
1775
|
}
|
|
1092
1776
|
);
|
|
1093
1777
|
}
|
|
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
1778
|
function positionOf3(index, orientation) {
|
|
1165
1779
|
const file = index & 7;
|
|
1166
1780
|
const rank = index >> 3;
|
|
@@ -1372,7 +1986,7 @@ function PromotionOverlay({
|
|
|
1372
1986
|
{
|
|
1373
1987
|
role: "dialog",
|
|
1374
1988
|
"aria-modal": "true",
|
|
1375
|
-
"aria-label": `Choose promotion piece at ${
|
|
1989
|
+
"aria-label": `Choose promotion piece at ${algebraicOf5(to)}`,
|
|
1376
1990
|
onClick: (e) => e.stopPropagation(),
|
|
1377
1991
|
style: {
|
|
1378
1992
|
position: "absolute",
|
|
@@ -1452,7 +2066,7 @@ function pieceName2(t) {
|
|
|
1452
2066
|
return "Piece";
|
|
1453
2067
|
}
|
|
1454
2068
|
}
|
|
1455
|
-
function
|
|
2069
|
+
function algebraicOf5(index) {
|
|
1456
2070
|
const file = index & 7;
|
|
1457
2071
|
const rank = index >> 3;
|
|
1458
2072
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1468,7 +2082,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1468
2082
|
/* @__PURE__ */ jsx(
|
|
1469
2083
|
"div",
|
|
1470
2084
|
{
|
|
1471
|
-
"data-piece-square":
|
|
2085
|
+
"data-piece-square": algebraicOf(index),
|
|
1472
2086
|
"data-piece-cell": cell,
|
|
1473
2087
|
style: {
|
|
1474
2088
|
...SLOT_BASE_STYLE,
|
|
@@ -1486,7 +2100,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1486
2100
|
}
|
|
1487
2101
|
var DEFAULT_DURATION_MS = 60;
|
|
1488
2102
|
var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
1489
|
-
function
|
|
2103
|
+
function algebraicOf6(index) {
|
|
1490
2104
|
const file = index & 7;
|
|
1491
2105
|
const rank = index >> 3;
|
|
1492
2106
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1534,7 +2148,7 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1534
2148
|
case "en-passant":
|
|
1535
2149
|
case "promotion": {
|
|
1536
2150
|
const toEl = container.querySelector(
|
|
1537
|
-
`[data-piece-square="${
|
|
2151
|
+
`[data-piece-square="${algebraicOf6(d.to)}"]`
|
|
1538
2152
|
);
|
|
1539
2153
|
if (toEl === null) break;
|
|
1540
2154
|
const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
|
|
@@ -1543,10 +2157,10 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1543
2157
|
}
|
|
1544
2158
|
case "castle": {
|
|
1545
2159
|
const kingEl = container.querySelector(
|
|
1546
|
-
`[data-piece-square="${
|
|
2160
|
+
`[data-piece-square="${algebraicOf6(d.kingTo)}"]`
|
|
1547
2161
|
);
|
|
1548
2162
|
const rookEl = container.querySelector(
|
|
1549
|
-
`[data-piece-square="${
|
|
2163
|
+
`[data-piece-square="${algebraicOf6(d.rookTo)}"]`
|
|
1550
2164
|
);
|
|
1551
2165
|
if (kingEl !== null) {
|
|
1552
2166
|
const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
|
|
@@ -1590,39 +2204,6 @@ function AnimationRunner({
|
|
|
1590
2204
|
}, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
|
|
1591
2205
|
return null;
|
|
1592
2206
|
}
|
|
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
2207
|
function colorForModifiers(e, palette) {
|
|
1627
2208
|
if (e.shiftKey) return palette.shift;
|
|
1628
2209
|
if (e.altKey) return palette.alt;
|
|
@@ -1937,7 +2518,7 @@ function useDrag(options) {
|
|
|
1937
2518
|
controller.cancel();
|
|
1938
2519
|
return;
|
|
1939
2520
|
}
|
|
1940
|
-
const originLabel =
|
|
2521
|
+
const originLabel = algebraicOf2(evt.from);
|
|
1941
2522
|
originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
|
|
1942
2523
|
if (originEl !== null) {
|
|
1943
2524
|
originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
|
|
@@ -2186,7 +2767,7 @@ function useLastMoveController(model, squareRefs, enabled) {
|
|
|
2186
2767
|
const lastMove = snap.lastMove;
|
|
2187
2768
|
let from = null;
|
|
2188
2769
|
let to = null;
|
|
2189
|
-
if (lastMove !== null) {
|
|
2770
|
+
if (lastMove !== null && !isNullMove(lastMove)) {
|
|
2190
2771
|
from = lastMove & 63;
|
|
2191
2772
|
to = lastMove >> 6 & 63;
|
|
2192
2773
|
}
|
|
@@ -2596,6 +3177,9 @@ var ILLEGAL_FLASH_HOLD_MS = 320;
|
|
|
2596
3177
|
var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
|
|
2597
3178
|
var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
2598
3179
|
var UCI_TRANSITION_RE = /^([a-h][1-8])([a-h][1-8])([nbrq])?$/i;
|
|
3180
|
+
function isNullTransition(transition) {
|
|
3181
|
+
return isNullMoveUci(transition.uci);
|
|
3182
|
+
}
|
|
2599
3183
|
function prefersReducedMotion3() {
|
|
2600
3184
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2601
3185
|
return false;
|
|
@@ -2643,6 +3227,7 @@ function castleRookTransition(from, to) {
|
|
|
2643
3227
|
};
|
|
2644
3228
|
}
|
|
2645
3229
|
function transitionSquares(transition) {
|
|
3230
|
+
if (isNullTransition(transition)) return [];
|
|
2646
3231
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2647
3232
|
if (parsed === null) return [];
|
|
2648
3233
|
const moveFrom = parsed[1] ?? "";
|
|
@@ -2653,6 +3238,7 @@ function transitionSquares(transition) {
|
|
|
2653
3238
|
return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
|
|
2654
3239
|
}
|
|
2655
3240
|
function parseTransitionMove(transition) {
|
|
3241
|
+
if (isNullTransition(transition)) return null;
|
|
2656
3242
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2657
3243
|
if (parsed === null) return null;
|
|
2658
3244
|
const from = parsed[1];
|
|
@@ -3223,8 +3809,8 @@ function useChessGame(options = {}) {
|
|
|
3223
3809
|
}
|
|
3224
3810
|
|
|
3225
3811
|
// src/index.ts
|
|
3226
|
-
var PACKAGE_VERSION = "1.
|
|
3812
|
+
var PACKAGE_VERSION = "1.5.0";
|
|
3227
3813
|
|
|
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 };
|
|
3814
|
+
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
3815
|
//# sourceMappingURL=index.js.map
|
|
3230
3816
|
//# sourceMappingURL=index.js.map
|