gigaboard 1.4.2 → 1.5.1

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