gigaboard 1.4.1 → 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 +879 -259
- 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 +786 -198
- 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;
|
|
@@ -160,11 +901,12 @@ function computeDynamicAeroGeometry(text, style, fontSize, isTerminal = true) {
|
|
|
160
901
|
const resolvedStyle = style ?? "aero_chisel";
|
|
161
902
|
const hasText = Boolean(text && text.trim().length > 0);
|
|
162
903
|
if (!hasText) {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
904
|
+
const scale2 = Math.max(1, fontSize / 2.45);
|
|
905
|
+
const tipX = 2.8 * scale2;
|
|
906
|
+
const shoulderX = 0.2 * scale2;
|
|
907
|
+
const rearX = -1.2 * scale2;
|
|
908
|
+
const dockX2 = -1.8 * scale2;
|
|
909
|
+
const halfH2 = 2.2 * scale2;
|
|
168
910
|
const path2 = `M ${rearX},${-halfH2} L ${shoulderX},${-halfH2} L ${tipX},0 L ${shoulderX},${halfH2} L ${rearX},${halfH2} C ${dockX2},${halfH2} ${dockX2},${-halfH2} ${rearX},${-halfH2} Z`;
|
|
169
911
|
return {
|
|
170
912
|
path: path2,
|
|
@@ -177,16 +919,17 @@ function computeDynamicAeroGeometry(text, style, fontSize, isTerminal = true) {
|
|
|
177
919
|
const charWidth = fontSize * 0.6;
|
|
178
920
|
const textWidth = text.length * charWidth;
|
|
179
921
|
const halfTextW = textWidth / 2;
|
|
180
|
-
const
|
|
181
|
-
const
|
|
922
|
+
const scale = Math.max(1, fontSize / 2.45);
|
|
923
|
+
const padX = 0.75 * scale;
|
|
924
|
+
const cabinW = Math.max(3.2 * scale, halfTextW + padX);
|
|
182
925
|
const halfH = Math.max(2.3, fontSize * 0.9);
|
|
183
|
-
const rearInset = 0.65;
|
|
926
|
+
const rearInset = 0.65 * scale;
|
|
184
927
|
const dockX = -cabinW - rearInset;
|
|
185
928
|
let frontLen = 0;
|
|
186
929
|
let path = "";
|
|
187
930
|
if (resolvedStyle === "aero_chisel") {
|
|
188
931
|
if (isTerminal) {
|
|
189
|
-
const noseLead = 1.25;
|
|
932
|
+
const noseLead = 1.25 * scale;
|
|
190
933
|
const tipX = cabinW + noseLead;
|
|
191
934
|
frontLen = tipX;
|
|
192
935
|
path = `M ${-cabinW},${-halfH} L ${cabinW},${-halfH} L ${tipX},0 L ${cabinW},${halfH} L ${-cabinW},${halfH} C ${dockX},${halfH} ${dockX},${-halfH} ${-cabinW},${-halfH} Z`;
|
|
@@ -605,7 +1348,7 @@ function injectFocusRingStyles() {
|
|
|
605
1348
|
`;
|
|
606
1349
|
document.head.appendChild(style);
|
|
607
1350
|
}
|
|
608
|
-
function
|
|
1351
|
+
function algebraicOf3(index) {
|
|
609
1352
|
const file = index & 7;
|
|
610
1353
|
const rank = index >> 3;
|
|
611
1354
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -624,7 +1367,7 @@ function SquareImpl({
|
|
|
624
1367
|
const rank = index >> 3;
|
|
625
1368
|
const isLight = (file + rank) % 2 === 1;
|
|
626
1369
|
const cell = useSquareCell(model, index);
|
|
627
|
-
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) :
|
|
1370
|
+
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf3(index);
|
|
628
1371
|
const internalRef = useRef(null);
|
|
629
1372
|
const lastReportedFocusRef = useRef(false);
|
|
630
1373
|
const setRef = useCallback(
|
|
@@ -667,7 +1410,7 @@ function SquareImpl({
|
|
|
667
1410
|
"aria-rowindex": rank + 1,
|
|
668
1411
|
"aria-colindex": file + 1,
|
|
669
1412
|
tabIndex: isFocused ? 0 : -1,
|
|
670
|
-
"data-square":
|
|
1413
|
+
"data-square": algebraicOf3(index),
|
|
671
1414
|
"data-gb-square": "",
|
|
672
1415
|
"data-light": isLight ? "true" : "false",
|
|
673
1416
|
onClick: handleClick,
|
|
@@ -805,64 +1548,6 @@ function CheckLayerInner({ model, orientation }) {
|
|
|
805
1548
|
}
|
|
806
1549
|
);
|
|
807
1550
|
}
|
|
808
|
-
var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
|
|
809
|
-
function Coordinates({ orientation, ranksPosition = "left" }) {
|
|
810
|
-
const ranksOnRight = ranksPosition === "right";
|
|
811
|
-
return /* @__PURE__ */ jsxs(
|
|
812
|
-
"div",
|
|
813
|
-
{
|
|
814
|
-
"aria-hidden": "true",
|
|
815
|
-
style: {
|
|
816
|
-
position: "absolute",
|
|
817
|
-
inset: 0,
|
|
818
|
-
pointerEvents: "none",
|
|
819
|
-
fontSize: "min(1.6vmin, 11px)",
|
|
820
|
-
fontWeight: 600,
|
|
821
|
-
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
822
|
-
},
|
|
823
|
-
children: [
|
|
824
|
-
FILE_LETTERS.map((letter, idx) => {
|
|
825
|
-
const col = orientation === "white" ? idx : 7 - idx;
|
|
826
|
-
const bottomRank = orientation === "white" ? 0 : 7;
|
|
827
|
-
const isLightSquare = (col + bottomRank) % 2 === 1;
|
|
828
|
-
return /* @__PURE__ */ jsx(
|
|
829
|
-
"span",
|
|
830
|
-
{
|
|
831
|
-
style: {
|
|
832
|
-
position: "absolute",
|
|
833
|
-
left: `${col * 12.5 + 10}%`,
|
|
834
|
-
bottom: "1%",
|
|
835
|
-
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
836
|
-
},
|
|
837
|
-
children: letter
|
|
838
|
-
},
|
|
839
|
-
`file-${letter}`
|
|
840
|
-
);
|
|
841
|
-
}),
|
|
842
|
-
FILE_LETTERS.map((_, idx) => {
|
|
843
|
-
const rank = idx;
|
|
844
|
-
const row = orientation === "white" ? 7 - rank : rank;
|
|
845
|
-
const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
|
|
846
|
-
const isLightSquare = (edgeFile + rank) % 2 === 1;
|
|
847
|
-
const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
|
|
848
|
-
return /* @__PURE__ */ jsx(
|
|
849
|
-
"span",
|
|
850
|
-
{
|
|
851
|
-
style: {
|
|
852
|
-
position: "absolute",
|
|
853
|
-
top: `${row * 12.5 + 1}%`,
|
|
854
|
-
...sideStyle,
|
|
855
|
-
color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
|
|
856
|
-
},
|
|
857
|
-
children: rank + 1
|
|
858
|
-
},
|
|
859
|
-
`rank-${rank}`
|
|
860
|
-
);
|
|
861
|
-
})
|
|
862
|
-
]
|
|
863
|
-
}
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
1551
|
var WRAPPER_STYLE = Object.freeze({
|
|
867
1552
|
position: "absolute",
|
|
868
1553
|
top: 0,
|
|
@@ -1005,7 +1690,7 @@ function IllegalFlashLayer({ at, orientation }) {
|
|
|
1005
1690
|
}
|
|
1006
1691
|
);
|
|
1007
1692
|
}
|
|
1008
|
-
function
|
|
1693
|
+
function algebraicOf4(index) {
|
|
1009
1694
|
const file = index & 7;
|
|
1010
1695
|
const rank = index >> 3;
|
|
1011
1696
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1033,14 +1718,15 @@ function pieceName(cell) {
|
|
|
1033
1718
|
}
|
|
1034
1719
|
function announcementFor(lastMove, board, wasCapture) {
|
|
1035
1720
|
const { from, to, kind, promotion } = decodePackedMove(lastMove);
|
|
1721
|
+
if (kind === "pass") return "Passes.";
|
|
1036
1722
|
if (kind === "castle") {
|
|
1037
1723
|
const isKingside = (to & 7) > (from & 7);
|
|
1038
1724
|
return isKingside ? "Castles kingside." : "Castles queenside.";
|
|
1039
1725
|
}
|
|
1040
1726
|
const landed = board[to] ?? 0;
|
|
1041
1727
|
const name = pieceName(landed);
|
|
1042
|
-
const fromSq =
|
|
1043
|
-
const toSq =
|
|
1728
|
+
const fromSq = algebraicOf4(from);
|
|
1729
|
+
const toSq = algebraicOf4(to);
|
|
1044
1730
|
if (kind === "promotion" && promotion !== null) {
|
|
1045
1731
|
const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
|
|
1046
1732
|
const action = wasCapture ? "captures and promotes" : "promotes";
|
|
@@ -1089,76 +1775,6 @@ function LiveRegion({ model }) {
|
|
|
1089
1775
|
}
|
|
1090
1776
|
);
|
|
1091
1777
|
}
|
|
1092
|
-
function gridCoord(index, orientation) {
|
|
1093
|
-
const file = index & 7;
|
|
1094
|
-
const rank = index >> 3;
|
|
1095
|
-
const col = orientation === "white" ? file + 1 : 8 - file;
|
|
1096
|
-
const row = orientation === "white" ? 8 - rank : rank + 1;
|
|
1097
|
-
return { col, row };
|
|
1098
|
-
}
|
|
1099
|
-
function algebraicOf3(index) {
|
|
1100
|
-
const file = index & 7;
|
|
1101
|
-
const rank = index >> 3;
|
|
1102
|
-
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1103
|
-
}
|
|
1104
|
-
var SLOT_BASE_STYLE = Object.freeze({
|
|
1105
|
-
display: "flex",
|
|
1106
|
-
alignItems: "center",
|
|
1107
|
-
justifyContent: "center",
|
|
1108
|
-
pointerEvents: "none",
|
|
1109
|
-
containerType: "size"
|
|
1110
|
-
});
|
|
1111
|
-
function PieceSlotImpl({ index, model, orientation, pieces }) {
|
|
1112
|
-
const cell = useSquareCell(model, index);
|
|
1113
|
-
if (cell === 0) return null;
|
|
1114
|
-
const { col, row } = gridCoord(index, orientation);
|
|
1115
|
-
return /* @__PURE__ */ jsx(
|
|
1116
|
-
"div",
|
|
1117
|
-
{
|
|
1118
|
-
"data-piece-square": algebraicOf3(index),
|
|
1119
|
-
"data-piece-cell": cell,
|
|
1120
|
-
style: {
|
|
1121
|
-
...SLOT_BASE_STYLE,
|
|
1122
|
-
gridColumnStart: col,
|
|
1123
|
-
gridRowStart: row,
|
|
1124
|
-
filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
|
|
1125
|
-
},
|
|
1126
|
-
children: pieces({ cell, square: index })
|
|
1127
|
-
}
|
|
1128
|
-
);
|
|
1129
|
-
}
|
|
1130
|
-
var PieceSlot = memo(PieceSlotImpl);
|
|
1131
|
-
PieceSlot.displayName = "PieceSlot";
|
|
1132
|
-
var CONTAINER_STYLE = Object.freeze({
|
|
1133
|
-
position: "absolute",
|
|
1134
|
-
inset: 0,
|
|
1135
|
-
display: "grid",
|
|
1136
|
-
gridTemplateColumns: "repeat(8, 1fr)",
|
|
1137
|
-
gridTemplateRows: "repeat(8, 1fr)",
|
|
1138
|
-
pointerEvents: "none"
|
|
1139
|
-
});
|
|
1140
|
-
var PieceLayer = memo(function PieceLayer2({
|
|
1141
|
-
model,
|
|
1142
|
-
orientation,
|
|
1143
|
-
pieces
|
|
1144
|
-
}) {
|
|
1145
|
-
const slots = [];
|
|
1146
|
-
for (let i = 0; i < 64; i++) {
|
|
1147
|
-
slots.push(
|
|
1148
|
-
/* @__PURE__ */ jsx(
|
|
1149
|
-
PieceSlot,
|
|
1150
|
-
{
|
|
1151
|
-
index: i,
|
|
1152
|
-
model,
|
|
1153
|
-
orientation,
|
|
1154
|
-
pieces
|
|
1155
|
-
},
|
|
1156
|
-
i
|
|
1157
|
-
)
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
|
|
1161
|
-
});
|
|
1162
1778
|
function positionOf3(index, orientation) {
|
|
1163
1779
|
const file = index & 7;
|
|
1164
1780
|
const rank = index >> 3;
|
|
@@ -1370,7 +1986,7 @@ function PromotionOverlay({
|
|
|
1370
1986
|
{
|
|
1371
1987
|
role: "dialog",
|
|
1372
1988
|
"aria-modal": "true",
|
|
1373
|
-
"aria-label": `Choose promotion piece at ${
|
|
1989
|
+
"aria-label": `Choose promotion piece at ${algebraicOf5(to)}`,
|
|
1374
1990
|
onClick: (e) => e.stopPropagation(),
|
|
1375
1991
|
style: {
|
|
1376
1992
|
position: "absolute",
|
|
@@ -1450,7 +2066,7 @@ function pieceName2(t) {
|
|
|
1450
2066
|
return "Piece";
|
|
1451
2067
|
}
|
|
1452
2068
|
}
|
|
1453
|
-
function
|
|
2069
|
+
function algebraicOf5(index) {
|
|
1454
2070
|
const file = index & 7;
|
|
1455
2071
|
const rank = index >> 3;
|
|
1456
2072
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1466,7 +2082,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1466
2082
|
/* @__PURE__ */ jsx(
|
|
1467
2083
|
"div",
|
|
1468
2084
|
{
|
|
1469
|
-
"data-piece-square":
|
|
2085
|
+
"data-piece-square": algebraicOf(index),
|
|
1470
2086
|
"data-piece-cell": cell,
|
|
1471
2087
|
style: {
|
|
1472
2088
|
...SLOT_BASE_STYLE,
|
|
@@ -1484,7 +2100,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
|
|
|
1484
2100
|
}
|
|
1485
2101
|
var DEFAULT_DURATION_MS = 60;
|
|
1486
2102
|
var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
1487
|
-
function
|
|
2103
|
+
function algebraicOf6(index) {
|
|
1488
2104
|
const file = index & 7;
|
|
1489
2105
|
const rank = index >> 3;
|
|
1490
2106
|
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
@@ -1532,7 +2148,7 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1532
2148
|
case "en-passant":
|
|
1533
2149
|
case "promotion": {
|
|
1534
2150
|
const toEl = container.querySelector(
|
|
1535
|
-
`[data-piece-square="${
|
|
2151
|
+
`[data-piece-square="${algebraicOf6(d.to)}"]`
|
|
1536
2152
|
);
|
|
1537
2153
|
if (toEl === null) break;
|
|
1538
2154
|
const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
|
|
@@ -1541,10 +2157,10 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
|
1541
2157
|
}
|
|
1542
2158
|
case "castle": {
|
|
1543
2159
|
const kingEl = container.querySelector(
|
|
1544
|
-
`[data-piece-square="${
|
|
2160
|
+
`[data-piece-square="${algebraicOf6(d.kingTo)}"]`
|
|
1545
2161
|
);
|
|
1546
2162
|
const rookEl = container.querySelector(
|
|
1547
|
-
`[data-piece-square="${
|
|
2163
|
+
`[data-piece-square="${algebraicOf6(d.rookTo)}"]`
|
|
1548
2164
|
);
|
|
1549
2165
|
if (kingEl !== null) {
|
|
1550
2166
|
const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
|
|
@@ -1588,39 +2204,6 @@ function AnimationRunner({
|
|
|
1588
2204
|
}, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
|
|
1589
2205
|
return null;
|
|
1590
2206
|
}
|
|
1591
|
-
|
|
1592
|
-
// src/lib/geometry.ts
|
|
1593
|
-
function getSquareAtPoint(container, clientX, clientY, orientation) {
|
|
1594
|
-
const rect = container.getBoundingClientRect();
|
|
1595
|
-
const relX = clientX - rect.left;
|
|
1596
|
-
const relY = clientY - rect.top;
|
|
1597
|
-
if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
|
|
1598
|
-
const col = Math.floor(relX / rect.width * 8);
|
|
1599
|
-
const row = Math.floor(relY / rect.height * 8);
|
|
1600
|
-
const file = orientation === "white" ? col : 7 - col;
|
|
1601
|
-
const rank = orientation === "white" ? 7 - row : row;
|
|
1602
|
-
return rank * 8 + file;
|
|
1603
|
-
}
|
|
1604
|
-
function getPointAtSquareCentre(container, square, orientation) {
|
|
1605
|
-
const rect = container.getBoundingClientRect();
|
|
1606
|
-
const sqW = rect.width / 8;
|
|
1607
|
-
const sqH = rect.height / 8;
|
|
1608
|
-
const file = square & 7;
|
|
1609
|
-
const rank = square >> 3;
|
|
1610
|
-
const col = orientation === "white" ? file : 7 - file;
|
|
1611
|
-
const row = orientation === "white" ? 7 - rank : rank;
|
|
1612
|
-
return {
|
|
1613
|
-
x: rect.left + sqW * col + sqW / 2,
|
|
1614
|
-
y: rect.top + sqH * row + sqH / 2
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
function algebraicOf6(square) {
|
|
1618
|
-
const file = square & 7;
|
|
1619
|
-
const rank = square >> 3;
|
|
1620
|
-
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// src/hooks/use-arrow-gesture.ts
|
|
1624
2207
|
function colorForModifiers(e, palette) {
|
|
1625
2208
|
if (e.shiftKey) return palette.shift;
|
|
1626
2209
|
if (e.altKey) return palette.alt;
|
|
@@ -1935,7 +2518,7 @@ function useDrag(options) {
|
|
|
1935
2518
|
controller.cancel();
|
|
1936
2519
|
return;
|
|
1937
2520
|
}
|
|
1938
|
-
const originLabel =
|
|
2521
|
+
const originLabel = algebraicOf2(evt.from);
|
|
1939
2522
|
originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
|
|
1940
2523
|
if (originEl !== null) {
|
|
1941
2524
|
originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
|
|
@@ -2184,7 +2767,7 @@ function useLastMoveController(model, squareRefs, enabled) {
|
|
|
2184
2767
|
const lastMove = snap.lastMove;
|
|
2185
2768
|
let from = null;
|
|
2186
2769
|
let to = null;
|
|
2187
|
-
if (lastMove !== null) {
|
|
2770
|
+
if (lastMove !== null && !isNullMove(lastMove)) {
|
|
2188
2771
|
from = lastMove & 63;
|
|
2189
2772
|
to = lastMove >> 6 & 63;
|
|
2190
2773
|
}
|
|
@@ -2594,6 +3177,9 @@ var ILLEGAL_FLASH_HOLD_MS = 320;
|
|
|
2594
3177
|
var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
|
|
2595
3178
|
var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
2596
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
|
+
}
|
|
2597
3183
|
function prefersReducedMotion3() {
|
|
2598
3184
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2599
3185
|
return false;
|
|
@@ -2641,6 +3227,7 @@ function castleRookTransition(from, to) {
|
|
|
2641
3227
|
};
|
|
2642
3228
|
}
|
|
2643
3229
|
function transitionSquares(transition) {
|
|
3230
|
+
if (isNullTransition(transition)) return [];
|
|
2644
3231
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2645
3232
|
if (parsed === null) return [];
|
|
2646
3233
|
const moveFrom = parsed[1] ?? "";
|
|
@@ -2651,6 +3238,7 @@ function transitionSquares(transition) {
|
|
|
2651
3238
|
return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
|
|
2652
3239
|
}
|
|
2653
3240
|
function parseTransitionMove(transition) {
|
|
3241
|
+
if (isNullTransition(transition)) return null;
|
|
2654
3242
|
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2655
3243
|
if (parsed === null) return null;
|
|
2656
3244
|
const from = parsed[1];
|
|
@@ -3221,8 +3809,8 @@ function useChessGame(options = {}) {
|
|
|
3221
3809
|
}
|
|
3222
3810
|
|
|
3223
3811
|
// src/index.ts
|
|
3224
|
-
var PACKAGE_VERSION = "1.
|
|
3812
|
+
var PACKAGE_VERSION = "1.5.0";
|
|
3225
3813
|
|
|
3226
|
-
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 };
|
|
3227
3815
|
//# sourceMappingURL=index.js.map
|
|
3228
3816
|
//# sourceMappingURL=index.js.map
|