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.
Files changed (33) hide show
  1. package/dist/{board-model-BMtkLQvL.d.cts → board-model-BIVxSCWC.d.cts} +73 -2
  2. package/dist/{board-model-BMtkLQvL.d.ts → board-model-BIVxSCWC.d.ts} +73 -2
  3. package/dist/{chunk-EJUGH25P.js → chunk-53XWDREK.js} +4 -3
  4. package/dist/chunk-53XWDREK.js.map +1 -0
  5. package/dist/{chunk-SO6AOEFF.js → chunk-6F3FZAG7.js} +116 -18
  6. package/dist/chunk-6F3FZAG7.js.map +1 -0
  7. package/dist/{chunk-UBD62NOI.cjs → chunk-FUP2LDMO.cjs} +121 -16
  8. package/dist/chunk-FUP2LDMO.cjs.map +1 -0
  9. package/dist/{chunk-D45G3J5E.cjs → chunk-N4WTPXFC.cjs} +4 -2
  10. package/dist/chunk-N4WTPXFC.cjs.map +1 -0
  11. package/dist/core.cjs +72 -44
  12. package/dist/core.d.cts +17 -12
  13. package/dist/core.d.ts +17 -12
  14. package/dist/core.js +1 -1
  15. package/dist/{gigachess-C08uAeFw.d.ts → gigachess-DN-ncmAU.d.ts} +1 -1
  16. package/dist/{gigachess-Dr5gCN68.d.cts → gigachess-XpNGPM2m.d.cts} +1 -1
  17. package/dist/index.cjs +879 -259
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +55 -8
  20. package/dist/index.d.ts +55 -8
  21. package/dist/index.js +786 -198
  22. package/dist/index.js.map +1 -1
  23. package/dist/server.cjs +11 -11
  24. package/dist/server.d.cts +2 -2
  25. package/dist/server.d.ts +2 -2
  26. package/dist/server.js +1 -1
  27. package/dist/{types-CcOgT5ov.d.ts → types-4W8ULiE2.d.ts} +1 -1
  28. package/dist/{types-DfqRXtbD.d.cts → types-Dox7oLof.d.cts} +1 -1
  29. package/package.json +2 -2
  30. package/dist/chunk-D45G3J5E.cjs.map +0 -1
  31. package/dist/chunk-EJUGH25P.js.map +0 -1
  32. package/dist/chunk-SO6AOEFF.js.map +0 -1
  33. 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-SO6AOEFF.js';
2
- export { Color, PieceType, createBoardModel, createGigachessAdapter, packMove, preloadGigachessAdapter, unpackMove } from './chunk-SO6AOEFF.js';
3
- import { CSS_VARS, defaultTheme, defaultPieces, parseFenPlacement, defaultArrowColors, DEFAULT_ILLEGAL_FLASH_TINT, DEFAULT_PREMOVE_TINT } from './chunk-EJUGH25P.js';
4
- export { CSS_VARS, DEFAULT_ILLEGAL_FLASH_TINT, DEFAULT_PREMOVE_TINT, defaultArrowColors, defaultPieces, defaultTheme } from './chunk-EJUGH25P.js';
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, jsx } from 'react/jsx-runtime';
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 tipX = 2.8;
164
- const shoulderX = 0.2;
165
- const rearX = -1.2;
166
- const dockX2 = -1.8;
167
- const halfH2 = 2.2;
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 padX = 0.75;
181
- const cabinW = Math.max(3.2, halfTextW + padX);
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 algebraicOf(index) {
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) : algebraicOf(index);
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": algebraicOf(index),
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 algebraicOf2(index) {
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 = algebraicOf2(from);
1043
- const toSq = algebraicOf2(to);
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 ${algebraicOf4(to)}`,
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 algebraicOf4(index) {
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": algebraicOf3(index),
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 algebraicOf5(index) {
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="${algebraicOf5(d.to)}"]`
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="${algebraicOf5(d.kingTo)}"]`
2160
+ `[data-piece-square="${algebraicOf6(d.kingTo)}"]`
1545
2161
  );
1546
2162
  const rookEl = container.querySelector(
1547
- `[data-piece-square="${algebraicOf5(d.rookTo)}"]`
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 = algebraicOf6(evt.from);
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.3.3";
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