gigaboard 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +868 -250
  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 +775 -189
  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;
@@ -607,7 +1348,7 @@ function injectFocusRingStyles() {
607
1348
  `;
608
1349
  document.head.appendChild(style);
609
1350
  }
610
- function algebraicOf(index) {
1351
+ function algebraicOf3(index) {
611
1352
  const file = index & 7;
612
1353
  const rank = index >> 3;
613
1354
  return `${String.fromCharCode(97 + file)}${rank + 1}`;
@@ -626,7 +1367,7 @@ function SquareImpl({
626
1367
  const rank = index >> 3;
627
1368
  const isLight = (file + rank) % 2 === 1;
628
1369
  const cell = useSquareCell(model, index);
629
- const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf(index);
1370
+ const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf3(index);
630
1371
  const internalRef = useRef(null);
631
1372
  const lastReportedFocusRef = useRef(false);
632
1373
  const setRef = useCallback(
@@ -669,7 +1410,7 @@ function SquareImpl({
669
1410
  "aria-rowindex": rank + 1,
670
1411
  "aria-colindex": file + 1,
671
1412
  tabIndex: isFocused ? 0 : -1,
672
- "data-square": algebraicOf(index),
1413
+ "data-square": algebraicOf3(index),
673
1414
  "data-gb-square": "",
674
1415
  "data-light": isLight ? "true" : "false",
675
1416
  onClick: handleClick,
@@ -807,64 +1548,6 @@ function CheckLayerInner({ model, orientation }) {
807
1548
  }
808
1549
  );
809
1550
  }
810
- var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
811
- function Coordinates({ orientation, ranksPosition = "left" }) {
812
- const ranksOnRight = ranksPosition === "right";
813
- return /* @__PURE__ */ jsxs(
814
- "div",
815
- {
816
- "aria-hidden": "true",
817
- style: {
818
- position: "absolute",
819
- inset: 0,
820
- pointerEvents: "none",
821
- fontSize: "min(1.6vmin, 11px)",
822
- fontWeight: 600,
823
- fontFamily: "system-ui, -apple-system, sans-serif"
824
- },
825
- children: [
826
- FILE_LETTERS.map((letter, idx) => {
827
- const col = orientation === "white" ? idx : 7 - idx;
828
- const bottomRank = orientation === "white" ? 0 : 7;
829
- const isLightSquare = (col + bottomRank) % 2 === 1;
830
- return /* @__PURE__ */ jsx(
831
- "span",
832
- {
833
- style: {
834
- position: "absolute",
835
- left: `${col * 12.5 + 10}%`,
836
- bottom: "1%",
837
- color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
838
- },
839
- children: letter
840
- },
841
- `file-${letter}`
842
- );
843
- }),
844
- FILE_LETTERS.map((_, idx) => {
845
- const rank = idx;
846
- const row = orientation === "white" ? 7 - rank : rank;
847
- const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
848
- const isLightSquare = (edgeFile + rank) % 2 === 1;
849
- const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
850
- return /* @__PURE__ */ jsx(
851
- "span",
852
- {
853
- style: {
854
- position: "absolute",
855
- top: `${row * 12.5 + 1}%`,
856
- ...sideStyle,
857
- color: `var(${isLightSquare ? CSS_VARS.COORDINATE_LIGHT : CSS_VARS.COORDINATE_DARK})`
858
- },
859
- children: rank + 1
860
- },
861
- `rank-${rank}`
862
- );
863
- })
864
- ]
865
- }
866
- );
867
- }
868
1551
  var WRAPPER_STYLE = Object.freeze({
869
1552
  position: "absolute",
870
1553
  top: 0,
@@ -1007,7 +1690,7 @@ function IllegalFlashLayer({ at, orientation }) {
1007
1690
  }
1008
1691
  );
1009
1692
  }
1010
- function algebraicOf2(index) {
1693
+ function algebraicOf4(index) {
1011
1694
  const file = index & 7;
1012
1695
  const rank = index >> 3;
1013
1696
  return `${String.fromCharCode(97 + file)}${rank + 1}`;
@@ -1035,14 +1718,15 @@ function pieceName(cell) {
1035
1718
  }
1036
1719
  function announcementFor(lastMove, board, wasCapture) {
1037
1720
  const { from, to, kind, promotion } = decodePackedMove(lastMove);
1721
+ if (kind === "pass") return "Passes.";
1038
1722
  if (kind === "castle") {
1039
1723
  const isKingside = (to & 7) > (from & 7);
1040
1724
  return isKingside ? "Castles kingside." : "Castles queenside.";
1041
1725
  }
1042
1726
  const landed = board[to] ?? 0;
1043
1727
  const name = pieceName(landed);
1044
- const fromSq = algebraicOf2(from);
1045
- const toSq = algebraicOf2(to);
1728
+ const fromSq = algebraicOf4(from);
1729
+ const toSq = algebraicOf4(to);
1046
1730
  if (kind === "promotion" && promotion !== null) {
1047
1731
  const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
1048
1732
  const action = wasCapture ? "captures and promotes" : "promotes";
@@ -1091,76 +1775,6 @@ function LiveRegion({ model }) {
1091
1775
  }
1092
1776
  );
1093
1777
  }
1094
- function gridCoord(index, orientation) {
1095
- const file = index & 7;
1096
- const rank = index >> 3;
1097
- const col = orientation === "white" ? file + 1 : 8 - file;
1098
- const row = orientation === "white" ? 8 - rank : rank + 1;
1099
- return { col, row };
1100
- }
1101
- function algebraicOf3(index) {
1102
- const file = index & 7;
1103
- const rank = index >> 3;
1104
- return `${String.fromCharCode(97 + file)}${rank + 1}`;
1105
- }
1106
- var SLOT_BASE_STYLE = Object.freeze({
1107
- display: "flex",
1108
- alignItems: "center",
1109
- justifyContent: "center",
1110
- pointerEvents: "none",
1111
- containerType: "size"
1112
- });
1113
- function PieceSlotImpl({ index, model, orientation, pieces }) {
1114
- const cell = useSquareCell(model, index);
1115
- if (cell === 0) return null;
1116
- const { col, row } = gridCoord(index, orientation);
1117
- return /* @__PURE__ */ jsx(
1118
- "div",
1119
- {
1120
- "data-piece-square": algebraicOf3(index),
1121
- "data-piece-cell": cell,
1122
- style: {
1123
- ...SLOT_BASE_STYLE,
1124
- gridColumnStart: col,
1125
- gridRowStart: row,
1126
- filter: `var(${cell <= 6 ? CSS_VARS.PIECE_FILTER_WHITE : CSS_VARS.PIECE_FILTER_BLACK}, none)`
1127
- },
1128
- children: pieces({ cell, square: index })
1129
- }
1130
- );
1131
- }
1132
- var PieceSlot = memo(PieceSlotImpl);
1133
- PieceSlot.displayName = "PieceSlot";
1134
- var CONTAINER_STYLE = Object.freeze({
1135
- position: "absolute",
1136
- inset: 0,
1137
- display: "grid",
1138
- gridTemplateColumns: "repeat(8, 1fr)",
1139
- gridTemplateRows: "repeat(8, 1fr)",
1140
- pointerEvents: "none"
1141
- });
1142
- var PieceLayer = memo(function PieceLayer2({
1143
- model,
1144
- orientation,
1145
- pieces
1146
- }) {
1147
- const slots = [];
1148
- for (let i = 0; i < 64; i++) {
1149
- slots.push(
1150
- /* @__PURE__ */ jsx(
1151
- PieceSlot,
1152
- {
1153
- index: i,
1154
- model,
1155
- orientation,
1156
- pieces
1157
- },
1158
- i
1159
- )
1160
- );
1161
- }
1162
- return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
1163
- });
1164
1778
  function positionOf3(index, orientation) {
1165
1779
  const file = index & 7;
1166
1780
  const rank = index >> 3;
@@ -1372,7 +1986,7 @@ function PromotionOverlay({
1372
1986
  {
1373
1987
  role: "dialog",
1374
1988
  "aria-modal": "true",
1375
- "aria-label": `Choose promotion piece at ${algebraicOf4(to)}`,
1989
+ "aria-label": `Choose promotion piece at ${algebraicOf5(to)}`,
1376
1990
  onClick: (e) => e.stopPropagation(),
1377
1991
  style: {
1378
1992
  position: "absolute",
@@ -1452,7 +2066,7 @@ function pieceName2(t) {
1452
2066
  return "Piece";
1453
2067
  }
1454
2068
  }
1455
- function algebraicOf4(index) {
2069
+ function algebraicOf5(index) {
1456
2070
  const file = index & 7;
1457
2071
  const rank = index >> 3;
1458
2072
  return `${String.fromCharCode(97 + file)}${rank + 1}`;
@@ -1468,7 +2082,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
1468
2082
  /* @__PURE__ */ jsx(
1469
2083
  "div",
1470
2084
  {
1471
- "data-piece-square": algebraicOf3(index),
2085
+ "data-piece-square": algebraicOf(index),
1472
2086
  "data-piece-cell": cell,
1473
2087
  style: {
1474
2088
  ...SLOT_BASE_STYLE,
@@ -1486,7 +2100,7 @@ function StaticPieceLayer({ board, orientation, pieces }) {
1486
2100
  }
1487
2101
  var DEFAULT_DURATION_MS = 60;
1488
2102
  var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
1489
- function algebraicOf5(index) {
2103
+ function algebraicOf6(index) {
1490
2104
  const file = index & 7;
1491
2105
  const rank = index >> 3;
1492
2106
  return `${String.fromCharCode(97 + file)}${rank + 1}`;
@@ -1534,7 +2148,7 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
1534
2148
  case "en-passant":
1535
2149
  case "promotion": {
1536
2150
  const toEl = container.querySelector(
1537
- `[data-piece-square="${algebraicOf5(d.to)}"]`
2151
+ `[data-piece-square="${algebraicOf6(d.to)}"]`
1538
2152
  );
1539
2153
  if (toEl === null) break;
1540
2154
  const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
@@ -1543,10 +2157,10 @@ function runAnimations(descriptors, container, orientation, duration, easing) {
1543
2157
  }
1544
2158
  case "castle": {
1545
2159
  const kingEl = container.querySelector(
1546
- `[data-piece-square="${algebraicOf5(d.kingTo)}"]`
2160
+ `[data-piece-square="${algebraicOf6(d.kingTo)}"]`
1547
2161
  );
1548
2162
  const rookEl = container.querySelector(
1549
- `[data-piece-square="${algebraicOf5(d.rookTo)}"]`
2163
+ `[data-piece-square="${algebraicOf6(d.rookTo)}"]`
1550
2164
  );
1551
2165
  if (kingEl !== null) {
1552
2166
  const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
@@ -1590,39 +2204,6 @@ function AnimationRunner({
1590
2204
  }, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
1591
2205
  return null;
1592
2206
  }
1593
-
1594
- // src/lib/geometry.ts
1595
- function getSquareAtPoint(container, clientX, clientY, orientation) {
1596
- const rect = container.getBoundingClientRect();
1597
- const relX = clientX - rect.left;
1598
- const relY = clientY - rect.top;
1599
- if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
1600
- const col = Math.floor(relX / rect.width * 8);
1601
- const row = Math.floor(relY / rect.height * 8);
1602
- const file = orientation === "white" ? col : 7 - col;
1603
- const rank = orientation === "white" ? 7 - row : row;
1604
- return rank * 8 + file;
1605
- }
1606
- function getPointAtSquareCentre(container, square, orientation) {
1607
- const rect = container.getBoundingClientRect();
1608
- const sqW = rect.width / 8;
1609
- const sqH = rect.height / 8;
1610
- const file = square & 7;
1611
- const rank = square >> 3;
1612
- const col = orientation === "white" ? file : 7 - file;
1613
- const row = orientation === "white" ? 7 - rank : rank;
1614
- return {
1615
- x: rect.left + sqW * col + sqW / 2,
1616
- y: rect.top + sqH * row + sqH / 2
1617
- };
1618
- }
1619
- function algebraicOf6(square) {
1620
- const file = square & 7;
1621
- const rank = square >> 3;
1622
- return `${String.fromCharCode(97 + file)}${rank + 1}`;
1623
- }
1624
-
1625
- // src/hooks/use-arrow-gesture.ts
1626
2207
  function colorForModifiers(e, palette) {
1627
2208
  if (e.shiftKey) return palette.shift;
1628
2209
  if (e.altKey) return palette.alt;
@@ -1937,7 +2518,7 @@ function useDrag(options) {
1937
2518
  controller.cancel();
1938
2519
  return;
1939
2520
  }
1940
- const originLabel = algebraicOf6(evt.from);
2521
+ const originLabel = algebraicOf2(evt.from);
1941
2522
  originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
1942
2523
  if (originEl !== null) {
1943
2524
  originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
@@ -2186,7 +2767,7 @@ function useLastMoveController(model, squareRefs, enabled) {
2186
2767
  const lastMove = snap.lastMove;
2187
2768
  let from = null;
2188
2769
  let to = null;
2189
- if (lastMove !== null) {
2770
+ if (lastMove !== null && !isNullMove(lastMove)) {
2190
2771
  from = lastMove & 63;
2191
2772
  to = lastMove >> 6 & 63;
2192
2773
  }
@@ -2596,6 +3177,9 @@ var ILLEGAL_FLASH_HOLD_MS = 320;
2596
3177
  var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
2597
3178
  var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
2598
3179
  var UCI_TRANSITION_RE = /^([a-h][1-8])([a-h][1-8])([nbrq])?$/i;
3180
+ function isNullTransition(transition) {
3181
+ return isNullMoveUci(transition.uci);
3182
+ }
2599
3183
  function prefersReducedMotion3() {
2600
3184
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
2601
3185
  return false;
@@ -2643,6 +3227,7 @@ function castleRookTransition(from, to) {
2643
3227
  };
2644
3228
  }
2645
3229
  function transitionSquares(transition) {
3230
+ if (isNullTransition(transition)) return [];
2646
3231
  const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
2647
3232
  if (parsed === null) return [];
2648
3233
  const moveFrom = parsed[1] ?? "";
@@ -2653,6 +3238,7 @@ function transitionSquares(transition) {
2653
3238
  return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
2654
3239
  }
2655
3240
  function parseTransitionMove(transition) {
3241
+ if (isNullTransition(transition)) return null;
2656
3242
  const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
2657
3243
  if (parsed === null) return null;
2658
3244
  const from = parsed[1];
@@ -3223,8 +3809,8 @@ function useChessGame(options = {}) {
3223
3809
  }
3224
3810
 
3225
3811
  // src/index.ts
3226
- var PACKAGE_VERSION = "1.3.3";
3812
+ var PACKAGE_VERSION = "1.5.0";
3227
3813
 
3228
- export { AnimationRunner, Chessboard, DEFAULT_MOVE_HAPTIC_PATTERNS, DEFAULT_MOVE_SOUND_SOURCES, PACKAGE_VERSION, classifyMoveFeedback, getPointAtSquareCentre, getSquareAtPoint, triggerHaptic, triggerMoveHaptic, useArrowGesture, useBoardSlice, useBoardSnapshot, useChessGame, useClickToMove, useDrag, useHoverSquare, useKeyboardNav, useMoveHaptics, useMoveSound, useSquareCell, warmupHaptics };
3814
+ export { AnimationRunner, BoardEditor, Chessboard, DEFAULT_MOVE_HAPTIC_PATTERNS, DEFAULT_MOVE_SOUND_SOURCES, PACKAGE_VERSION, algebraicOf2 as algebraicOf, classifyMoveFeedback, formatFenPlacement, getPointAtSquareCentre, getSquareAtPoint, squareOf, triggerHaptic, triggerMoveHaptic, useArrowGesture, useBoardSlice, useBoardSnapshot, useChessGame, useClickToMove, useDrag, useHoverSquare, useKeyboardNav, useMoveHaptics, useMoveSound, useSquareCell, warmupHaptics };
3229
3815
  //# sourceMappingURL=index.js.map
3230
3816
  //# sourceMappingURL=index.js.map