gigaboard 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/board-model-BvJOtwQ3.d.cts +675 -0
- package/dist/board-model-BvJOtwQ3.d.ts +675 -0
- package/dist/canvas.cjs +8 -0
- package/dist/canvas.cjs.map +1 -0
- package/dist/canvas.d.cts +14 -0
- package/dist/canvas.d.ts +14 -0
- package/dist/canvas.js +6 -0
- package/dist/canvas.js.map +1 -0
- package/dist/chunk-3HNIF4ZE.js +3 -0
- package/dist/chunk-3HNIF4ZE.js.map +1 -0
- package/dist/chunk-44YKPXXT.js +63 -0
- package/dist/chunk-44YKPXXT.js.map +1 -0
- package/dist/chunk-5WAOEWKS.js +16 -0
- package/dist/chunk-5WAOEWKS.js.map +1 -0
- package/dist/chunk-AYVQCHSG.cjs +10 -0
- package/dist/chunk-AYVQCHSG.cjs.map +1 -0
- package/dist/chunk-B26QLI2K.cjs +18 -0
- package/dist/chunk-B26QLI2K.cjs.map +1 -0
- package/dist/chunk-B2TBDDJN.cjs +10 -0
- package/dist/chunk-B2TBDDJN.cjs.map +1 -0
- package/dist/chunk-BFN5JI3T.js +8 -0
- package/dist/chunk-BFN5JI3T.js.map +1 -0
- package/dist/chunk-BWNBPN3P.cjs +10 -0
- package/dist/chunk-BWNBPN3P.cjs.map +1 -0
- package/dist/chunk-CXDGJMYL.js +105 -0
- package/dist/chunk-CXDGJMYL.js.map +1 -0
- package/dist/chunk-EOSX5UX3.js +8 -0
- package/dist/chunk-EOSX5UX3.js.map +1 -0
- package/dist/chunk-FNRS6YJI.cjs +67 -0
- package/dist/chunk-FNRS6YJI.cjs.map +1 -0
- package/dist/chunk-G6ZFOJZR.cjs +18 -0
- package/dist/chunk-G6ZFOJZR.cjs.map +1 -0
- package/dist/chunk-HSMILJKQ.cjs +18 -0
- package/dist/chunk-HSMILJKQ.cjs.map +1 -0
- package/dist/chunk-I4BQGFP5.cjs +113 -0
- package/dist/chunk-I4BQGFP5.cjs.map +1 -0
- package/dist/chunk-J6GGNP5S.js +1104 -0
- package/dist/chunk-J6GGNP5S.js.map +1 -0
- package/dist/chunk-JQSBAWIZ.cjs +4 -0
- package/dist/chunk-JQSBAWIZ.cjs.map +1 -0
- package/dist/chunk-KW56FEBY.js +8 -0
- package/dist/chunk-KW56FEBY.js.map +1 -0
- package/dist/chunk-N7ED2MZS.cjs +10 -0
- package/dist/chunk-N7ED2MZS.cjs.map +1 -0
- package/dist/chunk-NAA5O4BT.js +16 -0
- package/dist/chunk-NAA5O4BT.js.map +1 -0
- package/dist/chunk-OQLY5GJF.js +16 -0
- package/dist/chunk-OQLY5GJF.js.map +1 -0
- package/dist/chunk-P72B3YTG.js +16 -0
- package/dist/chunk-P72B3YTG.js.map +1 -0
- package/dist/chunk-U6WAD322.js +8 -0
- package/dist/chunk-U6WAD322.js.map +1 -0
- package/dist/chunk-UUTEFNFO.cjs +1148 -0
- package/dist/chunk-UUTEFNFO.cjs.map +1 -0
- package/dist/chunk-VYSVIIZA.cjs +18 -0
- package/dist/chunk-VYSVIIZA.cjs.map +1 -0
- package/dist/core.cjs +180 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +164 -0
- package/dist/core.d.ts +164 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/create-image-piece-set-BcmRKGql.d.cts +100 -0
- package/dist/create-image-piece-set-BcmRKGql.d.ts +100 -0
- package/dist/gigachess-3BDD--yT.d.cts +22 -0
- package/dist/gigachess-DolQlR82.d.ts +22 -0
- package/dist/index.cjs +2972 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +435 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +2898 -0
- package/dist/index.js.map +1 -0
- package/dist/pieces/alpha.cjs +13 -0
- package/dist/pieces/alpha.cjs.map +1 -0
- package/dist/pieces/alpha.d.cts +17 -0
- package/dist/pieces/alpha.d.ts +17 -0
- package/dist/pieces/alpha.js +4 -0
- package/dist/pieces/alpha.js.map +1 -0
- package/dist/pieces/cburnett.cjs +13 -0
- package/dist/pieces/cburnett.cjs.map +1 -0
- package/dist/pieces/cburnett.d.cts +19 -0
- package/dist/pieces/cburnett.d.ts +19 -0
- package/dist/pieces/cburnett.js +4 -0
- package/dist/pieces/cburnett.js.map +1 -0
- package/dist/pieces/chesscom.cjs +14 -0
- package/dist/pieces/chesscom.cjs.map +1 -0
- package/dist/pieces/chesscom.d.cts +3 -0
- package/dist/pieces/chesscom.d.ts +3 -0
- package/dist/pieces/chesscom.js +5 -0
- package/dist/pieces/chesscom.js.map +1 -0
- package/dist/pieces/merida.cjs +13 -0
- package/dist/pieces/merida.cjs.map +1 -0
- package/dist/pieces/merida.d.cts +17 -0
- package/dist/pieces/merida.d.ts +17 -0
- package/dist/pieces/merida.js +4 -0
- package/dist/pieces/merida.js.map +1 -0
- package/dist/pieces/neo.cjs +13 -0
- package/dist/pieces/neo.cjs.map +1 -0
- package/dist/pieces/neo.d.cts +18 -0
- package/dist/pieces/neo.d.ts +18 -0
- package/dist/pieces/neo.js +4 -0
- package/dist/pieces/neo.js.map +1 -0
- package/dist/pieces.cjs +55 -0
- package/dist/pieces.cjs.map +1 -0
- package/dist/pieces.d.cts +21 -0
- package/dist/pieces.d.ts +21 -0
- package/dist/pieces.js +24 -0
- package/dist/pieces.js.map +1 -0
- package/dist/server.cjs +207 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +60 -0
- package/dist/server.d.ts +60 -0
- package/dist/server.js +205 -0
- package/dist/server.js.map +1 -0
- package/dist/sounds/capture.mp3 +0 -0
- package/dist/sounds/castle.mp3 +0 -0
- package/dist/sounds/game-end.mp3 +0 -0
- package/dist/sounds/move-check.mp3 +0 -0
- package/dist/sounds/move-opponent.mp3 +0 -0
- package/dist/sounds/move-self.mp3 +0 -0
- package/dist/sounds/promote.mp3 +0 -0
- package/dist/themes/blue.cjs +12 -0
- package/dist/themes/blue.cjs.map +1 -0
- package/dist/themes/blue.d.cts +102 -0
- package/dist/themes/blue.d.ts +102 -0
- package/dist/themes/blue.js +3 -0
- package/dist/themes/blue.js.map +1 -0
- package/dist/themes/brown.cjs +12 -0
- package/dist/themes/brown.cjs.map +1 -0
- package/dist/themes/brown.d.cts +1 -0
- package/dist/themes/brown.d.ts +1 -0
- package/dist/themes/brown.js +3 -0
- package/dist/themes/brown.js.map +1 -0
- package/dist/themes/green.cjs +12 -0
- package/dist/themes/green.cjs.map +1 -0
- package/dist/themes/green.d.cts +1 -0
- package/dist/themes/green.d.ts +1 -0
- package/dist/themes/green.js +3 -0
- package/dist/themes/green.js.map +1 -0
- package/dist/themes/wood.cjs +12 -0
- package/dist/themes/wood.cjs.map +1 -0
- package/dist/themes/wood.d.cts +1 -0
- package/dist/themes/wood.d.ts +1 -0
- package/dist/themes/wood.js +3 -0
- package/dist/themes/wood.js.map +1 -0
- package/dist/themes.cjs +36 -0
- package/dist/themes.cjs.map +1 -0
- package/dist/themes.d.cts +1 -0
- package/dist/themes.d.ts +1 -0
- package/dist/themes.js +21 -0
- package/dist/themes.js.map +1 -0
- package/dist/types-Bg2xiUHE.d.cts +501 -0
- package/dist/types-D5eBJGzj.d.ts +501 -0
- package/package.json +210 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2972 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkUUTEFNFO_cjs = require('./chunk-UUTEFNFO.cjs');
|
|
4
|
+
var chunkI4BQGFP5_cjs = require('./chunk-I4BQGFP5.cjs');
|
|
5
|
+
require('./chunk-G6ZFOJZR.cjs');
|
|
6
|
+
require('./chunk-AYVQCHSG.cjs');
|
|
7
|
+
require('./chunk-FNRS6YJI.cjs');
|
|
8
|
+
var react = require('react');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
|
|
11
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
12
|
+
function useBoardSnapshot(model) {
|
|
13
|
+
return react.useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot);
|
|
14
|
+
}
|
|
15
|
+
function useBoardSlice(model, selector) {
|
|
16
|
+
const getSnapshot = react.useCallback(() => selector(model.getSnapshot()), [model, selector]);
|
|
17
|
+
return react.useSyncExternalStore(model.subscribe, getSnapshot, getSnapshot);
|
|
18
|
+
}
|
|
19
|
+
function useSquareCell(model, index) {
|
|
20
|
+
const subscribe = react.useCallback(
|
|
21
|
+
(listener) => model ? model.subscribeSquare(index, listener) : () => {
|
|
22
|
+
},
|
|
23
|
+
[model, index]
|
|
24
|
+
);
|
|
25
|
+
const getSnapshot = react.useCallback(() => {
|
|
26
|
+
if (!model) return 0;
|
|
27
|
+
const snap = model.getSnapshot();
|
|
28
|
+
return snap.board[index] ?? 0;
|
|
29
|
+
}, [model, index]);
|
|
30
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
31
|
+
}
|
|
32
|
+
function squareCentrePct(index, orientation) {
|
|
33
|
+
const file = index & 7;
|
|
34
|
+
const rank = index >> 3;
|
|
35
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
36
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
37
|
+
return { cx: col * 12.5 + 6.25, cy: row * 12.5 + 6.25 };
|
|
38
|
+
}
|
|
39
|
+
function isKnightMove(fromIndex, toIndex) {
|
|
40
|
+
const fileDelta = Math.abs((toIndex & 7) - (fromIndex & 7));
|
|
41
|
+
const rankDelta = Math.abs((toIndex >> 3) - (fromIndex >> 3));
|
|
42
|
+
return fileDelta === 1 && rankDelta === 2 || fileDelta === 2 && rankDelta === 1;
|
|
43
|
+
}
|
|
44
|
+
function unitVector(from, to) {
|
|
45
|
+
const dx = to.x - from.x;
|
|
46
|
+
const dy = to.y - from.y;
|
|
47
|
+
const len = Math.hypot(dx, dy);
|
|
48
|
+
if (len === 0) return null;
|
|
49
|
+
return { x: dx / len, y: dy / len };
|
|
50
|
+
}
|
|
51
|
+
function arrowPath(fromIndex, toIndex, start, end, startOffset, tipOffset, headLength) {
|
|
52
|
+
if (!isKnightMove(fromIndex, toIndex)) {
|
|
53
|
+
const unit = unitVector(start, end);
|
|
54
|
+
if (unit === null) return null;
|
|
55
|
+
const tip2 = { x: end.x - unit.x * tipOffset, y: end.y - unit.y * tipOffset };
|
|
56
|
+
return {
|
|
57
|
+
start: { x: start.x + unit.x * startOffset, y: start.y + unit.y * startOffset },
|
|
58
|
+
corner: null,
|
|
59
|
+
shaftEnd: { x: tip2.x - unit.x * headLength, y: tip2.y - unit.y * headLength },
|
|
60
|
+
tip: tip2,
|
|
61
|
+
finalUnit: unit
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const dx = end.x - start.x;
|
|
65
|
+
const dy = end.y - start.y;
|
|
66
|
+
const corner = Math.abs(dx) > Math.abs(dy) ? { x: end.x, y: start.y } : { x: start.x, y: end.y };
|
|
67
|
+
const firstUnit = unitVector(start, corner);
|
|
68
|
+
const finalUnit = unitVector(corner, end);
|
|
69
|
+
if (firstUnit === null || finalUnit === null) return null;
|
|
70
|
+
const tip = { x: end.x - finalUnit.x * tipOffset, y: end.y - finalUnit.y * tipOffset };
|
|
71
|
+
return {
|
|
72
|
+
start: { x: start.x + firstUnit.x * startOffset, y: start.y + firstUnit.y * startOffset },
|
|
73
|
+
corner,
|
|
74
|
+
shaftEnd: { x: tip.x - finalUnit.x * headLength, y: tip.y - finalUnit.y * headLength },
|
|
75
|
+
tip,
|
|
76
|
+
finalUnit
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function resolveColor(arrow, palette) {
|
|
80
|
+
if (arrow.brush !== void 0 && palette !== void 0) {
|
|
81
|
+
const brushed = palette[arrow.brush];
|
|
82
|
+
if (brushed !== void 0) return brushed;
|
|
83
|
+
}
|
|
84
|
+
return arrow.color;
|
|
85
|
+
}
|
|
86
|
+
function drawArrow(ctx, fromIndex, toIndex, color, sqSize, orientation, preview) {
|
|
87
|
+
const a = squareCentrePct(fromIndex, orientation);
|
|
88
|
+
const b = squareCentrePct(toIndex, orientation);
|
|
89
|
+
const boardSize = sqSize * 8;
|
|
90
|
+
const start = { x: a.cx / 100 * boardSize, y: a.cy / 100 * boardSize };
|
|
91
|
+
const end = { x: b.cx / 100 * boardSize, y: b.cy / 100 * boardSize };
|
|
92
|
+
if (fromIndex === toIndex) {
|
|
93
|
+
ctx.strokeStyle = color;
|
|
94
|
+
ctx.lineWidth = sqSize * 0.08;
|
|
95
|
+
ctx.globalAlpha = preview ? 0.65 : 1;
|
|
96
|
+
ctx.beginPath();
|
|
97
|
+
ctx.arc(start.x, start.y, sqSize * 0.44, 0, Math.PI * 2);
|
|
98
|
+
ctx.stroke();
|
|
99
|
+
ctx.globalAlpha = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const startOffset = sqSize * 0.28;
|
|
103
|
+
const tipOffset = sqSize * 0.08;
|
|
104
|
+
const headLength = sqSize * 0.36;
|
|
105
|
+
const headHalfWidth = sqSize * 0.28;
|
|
106
|
+
const shaftWidth = sqSize * 0.18;
|
|
107
|
+
const path = arrowPath(fromIndex, toIndex, start, end, startOffset, tipOffset, headLength);
|
|
108
|
+
if (path === null) return;
|
|
109
|
+
ctx.globalAlpha = preview ? 0.65 : 1;
|
|
110
|
+
ctx.strokeStyle = color;
|
|
111
|
+
ctx.lineWidth = shaftWidth;
|
|
112
|
+
ctx.lineCap = "butt";
|
|
113
|
+
ctx.lineJoin = "round";
|
|
114
|
+
ctx.beginPath();
|
|
115
|
+
ctx.moveTo(path.start.x, path.start.y);
|
|
116
|
+
if (path.corner !== null) ctx.lineTo(path.corner.x, path.corner.y);
|
|
117
|
+
ctx.lineTo(path.shaftEnd.x, path.shaftEnd.y);
|
|
118
|
+
ctx.stroke();
|
|
119
|
+
const perpX = -path.finalUnit.y;
|
|
120
|
+
const perpY = path.finalUnit.x;
|
|
121
|
+
ctx.fillStyle = color;
|
|
122
|
+
ctx.beginPath();
|
|
123
|
+
ctx.moveTo(path.tip.x, path.tip.y);
|
|
124
|
+
ctx.lineTo(path.shaftEnd.x + perpX * headHalfWidth, path.shaftEnd.y + perpY * headHalfWidth);
|
|
125
|
+
ctx.lineTo(path.shaftEnd.x - perpX * headHalfWidth, path.shaftEnd.y - perpY * headHalfWidth);
|
|
126
|
+
ctx.closePath();
|
|
127
|
+
ctx.fill();
|
|
128
|
+
ctx.globalAlpha = 1;
|
|
129
|
+
}
|
|
130
|
+
function redraw(canvas, arrows, preview, orientation, palette) {
|
|
131
|
+
const ctx = canvas.getContext("2d");
|
|
132
|
+
if (ctx === null) return;
|
|
133
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
134
|
+
const rect = canvas.getBoundingClientRect();
|
|
135
|
+
const cssWidth = rect.width;
|
|
136
|
+
const cssHeight = rect.height;
|
|
137
|
+
if (cssWidth <= 0 || cssHeight <= 0) return;
|
|
138
|
+
const backingWidth = Math.round(cssWidth * dpr);
|
|
139
|
+
const backingHeight = Math.round(cssHeight * dpr);
|
|
140
|
+
if (canvas.width !== backingWidth) canvas.width = backingWidth;
|
|
141
|
+
if (canvas.height !== backingHeight) canvas.height = backingHeight;
|
|
142
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
143
|
+
ctx.clearRect(0, 0, cssWidth, cssHeight);
|
|
144
|
+
const sqSize = cssWidth / 8;
|
|
145
|
+
for (const arrow of arrows) {
|
|
146
|
+
drawArrow(ctx, arrow.from, arrow.to, resolveColor(arrow, palette), sqSize, orientation, false);
|
|
147
|
+
}
|
|
148
|
+
if (preview !== null) {
|
|
149
|
+
drawArrow(ctx, preview.from, preview.to, preview.color, sqSize, orientation, true);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function anchorPct(from, to, orientation, center) {
|
|
153
|
+
const a = squareCentrePct(from, orientation);
|
|
154
|
+
const b = squareCentrePct(to, orientation);
|
|
155
|
+
if (center === "orig") return a;
|
|
156
|
+
if (center === "dest") return b;
|
|
157
|
+
return { cx: (a.cx + b.cx) / 2, cy: (a.cy + b.cy) / 2 };
|
|
158
|
+
}
|
|
159
|
+
var ArrowsLayer = react.memo(
|
|
160
|
+
react.forwardRef(function ArrowsLayer2({ model, orientation, palette, below = false }, handle) {
|
|
161
|
+
const canvasRef = react.useRef(null);
|
|
162
|
+
const previewRef = react.useRef(null);
|
|
163
|
+
const allArrows = useBoardSlice(model, (s) => s.arrows);
|
|
164
|
+
const arrows = react.useMemo(
|
|
165
|
+
() => allArrows.filter((a) => a.below === true === below),
|
|
166
|
+
[allArrows, below]
|
|
167
|
+
);
|
|
168
|
+
const hasDecorations = react.useMemo(
|
|
169
|
+
() => arrows.some((a) => a.label !== void 0 || a.customSvg !== void 0),
|
|
170
|
+
[arrows]
|
|
171
|
+
);
|
|
172
|
+
react.useImperativeHandle(
|
|
173
|
+
handle,
|
|
174
|
+
() => ({
|
|
175
|
+
setPreview(preview) {
|
|
176
|
+
if (below) return;
|
|
177
|
+
previewRef.current = preview;
|
|
178
|
+
const canvas = canvasRef.current;
|
|
179
|
+
if (canvas !== null) redraw(canvas, arrows, preview, orientation, palette);
|
|
180
|
+
}
|
|
181
|
+
}),
|
|
182
|
+
[arrows, orientation, palette, below]
|
|
183
|
+
);
|
|
184
|
+
react.useLayoutEffect(() => {
|
|
185
|
+
const canvas = canvasRef.current;
|
|
186
|
+
if (canvas === null) return;
|
|
187
|
+
redraw(canvas, arrows, previewRef.current, orientation, palette);
|
|
188
|
+
}, [arrows, orientation, palette]);
|
|
189
|
+
react.useEffect(() => {
|
|
190
|
+
const canvas = canvasRef.current;
|
|
191
|
+
if (canvas === null) return;
|
|
192
|
+
const parent = canvas.parentElement;
|
|
193
|
+
if (parent === null || typeof ResizeObserver === "undefined") return;
|
|
194
|
+
const observer = new ResizeObserver(() => {
|
|
195
|
+
redraw(canvas, arrows, previewRef.current, orientation, palette);
|
|
196
|
+
});
|
|
197
|
+
observer.observe(parent);
|
|
198
|
+
return () => observer.disconnect();
|
|
199
|
+
}, [arrows, orientation, palette]);
|
|
200
|
+
const layerZIndex = below ? void 0 : 15;
|
|
201
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
203
|
+
"canvas",
|
|
204
|
+
{
|
|
205
|
+
ref: canvasRef,
|
|
206
|
+
"data-layer": below ? "arrows-below" : "arrows",
|
|
207
|
+
style: {
|
|
208
|
+
position: "absolute",
|
|
209
|
+
inset: 0,
|
|
210
|
+
width: "100%",
|
|
211
|
+
height: "100%",
|
|
212
|
+
pointerEvents: "none",
|
|
213
|
+
...layerZIndex !== void 0 ? { zIndex: layerZIndex } : {}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
),
|
|
217
|
+
hasDecorations ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
218
|
+
"svg",
|
|
219
|
+
{
|
|
220
|
+
"data-layer": below ? "arrows-below-decor" : "arrows-decor",
|
|
221
|
+
viewBox: "0 0 100 100",
|
|
222
|
+
preserveAspectRatio: "none",
|
|
223
|
+
"aria-hidden": "true",
|
|
224
|
+
style: {
|
|
225
|
+
position: "absolute",
|
|
226
|
+
inset: 0,
|
|
227
|
+
width: "100%",
|
|
228
|
+
height: "100%",
|
|
229
|
+
pointerEvents: "none",
|
|
230
|
+
// Decorations share the z-plane of the canvas they annotate.
|
|
231
|
+
...layerZIndex !== void 0 ? { zIndex: layerZIndex } : {}
|
|
232
|
+
},
|
|
233
|
+
children: arrows.map((arrow) => {
|
|
234
|
+
const key = `${arrow.from}-${arrow.to}-${arrow.color}-${arrow.brush ?? ""}`;
|
|
235
|
+
const color = resolveColor(arrow, palette);
|
|
236
|
+
const nodes = [];
|
|
237
|
+
if (arrow.label !== void 0) {
|
|
238
|
+
const { cx, cy } = anchorPct(arrow.from, arrow.to, orientation, "label");
|
|
239
|
+
nodes.push(
|
|
240
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
241
|
+
"text",
|
|
242
|
+
{
|
|
243
|
+
x: cx,
|
|
244
|
+
y: cy,
|
|
245
|
+
fill: arrow.label.fill ?? color,
|
|
246
|
+
fontSize: "4",
|
|
247
|
+
fontWeight: "700",
|
|
248
|
+
textAnchor: "middle",
|
|
249
|
+
dominantBaseline: "central",
|
|
250
|
+
style: { userSelect: "none" },
|
|
251
|
+
children: arrow.label.text
|
|
252
|
+
},
|
|
253
|
+
`${key}-label`
|
|
254
|
+
)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (arrow.customSvg !== void 0) {
|
|
258
|
+
const { cx, cy } = anchorPct(
|
|
259
|
+
arrow.from,
|
|
260
|
+
arrow.to,
|
|
261
|
+
orientation,
|
|
262
|
+
arrow.customSvg.center ?? "dest"
|
|
263
|
+
);
|
|
264
|
+
nodes.push(
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
266
|
+
"g",
|
|
267
|
+
{
|
|
268
|
+
transform: `translate(${cx}, ${cy})`,
|
|
269
|
+
dangerouslySetInnerHTML: { __html: arrow.customSvg.html }
|
|
270
|
+
},
|
|
271
|
+
`${key}-svg`
|
|
272
|
+
)
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return nodes.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("g", { children: nodes }, key);
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
) : null
|
|
279
|
+
] });
|
|
280
|
+
})
|
|
281
|
+
);
|
|
282
|
+
var FOCUS_STYLE_ID = "gb-focus-ring-styles";
|
|
283
|
+
function injectFocusRingStyles() {
|
|
284
|
+
if (typeof document === "undefined") return;
|
|
285
|
+
if (document.getElementById(FOCUS_STYLE_ID) !== null) return;
|
|
286
|
+
const style = document.createElement("style");
|
|
287
|
+
style.id = FOCUS_STYLE_ID;
|
|
288
|
+
style.textContent = `
|
|
289
|
+
[data-gb-square]:focus-visible {
|
|
290
|
+
box-shadow: inset 0 0 0 3px rgba(255, 206, 76, 0.95);
|
|
291
|
+
}
|
|
292
|
+
`;
|
|
293
|
+
document.head.appendChild(style);
|
|
294
|
+
}
|
|
295
|
+
function algebraicOf(index) {
|
|
296
|
+
const file = index & 7;
|
|
297
|
+
const rank = index >> 3;
|
|
298
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
299
|
+
}
|
|
300
|
+
function SquareImpl({
|
|
301
|
+
index,
|
|
302
|
+
onClick,
|
|
303
|
+
renderSquare,
|
|
304
|
+
isFocused,
|
|
305
|
+
setSquareRef,
|
|
306
|
+
model,
|
|
307
|
+
getSquareAriaLabel,
|
|
308
|
+
onFocus
|
|
309
|
+
}) {
|
|
310
|
+
const file = index & 7;
|
|
311
|
+
const rank = index >> 3;
|
|
312
|
+
const isLight = (file + rank) % 2 === 1;
|
|
313
|
+
const cell = useSquareCell(model, index);
|
|
314
|
+
const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf(index);
|
|
315
|
+
const internalRef = react.useRef(null);
|
|
316
|
+
const lastReportedFocusRef = react.useRef(false);
|
|
317
|
+
const setRef = react.useCallback(
|
|
318
|
+
(el) => {
|
|
319
|
+
internalRef.current = el;
|
|
320
|
+
setSquareRef?.(index, el);
|
|
321
|
+
},
|
|
322
|
+
[index, setSquareRef]
|
|
323
|
+
);
|
|
324
|
+
react.useEffect(() => {
|
|
325
|
+
injectFocusRingStyles();
|
|
326
|
+
}, []);
|
|
327
|
+
react.useEffect(() => {
|
|
328
|
+
if (!isFocused) {
|
|
329
|
+
lastReportedFocusRef.current = false;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const el = internalRef.current;
|
|
333
|
+
if (el === null) return;
|
|
334
|
+
const grid = el.closest('[role="grid"]');
|
|
335
|
+
if (grid === null) return;
|
|
336
|
+
if (!grid.contains(document.activeElement)) return;
|
|
337
|
+
if (document.activeElement !== el) el.focus();
|
|
338
|
+
if (!lastReportedFocusRef.current) {
|
|
339
|
+
lastReportedFocusRef.current = true;
|
|
340
|
+
onFocus?.(index, cell);
|
|
341
|
+
}
|
|
342
|
+
}, [isFocused, index, cell, onFocus]);
|
|
343
|
+
const handleFocus = react.useCallback(() => {
|
|
344
|
+
lastReportedFocusRef.current = true;
|
|
345
|
+
onFocus?.(index, cell);
|
|
346
|
+
}, [index, cell, onFocus]);
|
|
347
|
+
const handleClick = () => onClick(index);
|
|
348
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
349
|
+
"div",
|
|
350
|
+
{
|
|
351
|
+
ref: setRef,
|
|
352
|
+
role: "gridcell",
|
|
353
|
+
"aria-label": label,
|
|
354
|
+
"aria-rowindex": rank + 1,
|
|
355
|
+
"aria-colindex": file + 1,
|
|
356
|
+
tabIndex: isFocused ? 0 : -1,
|
|
357
|
+
"data-square": algebraicOf(index),
|
|
358
|
+
"data-gb-square": "",
|
|
359
|
+
"data-light": isLight ? "true" : "false",
|
|
360
|
+
onClick: handleClick,
|
|
361
|
+
onFocus: handleFocus,
|
|
362
|
+
style: {
|
|
363
|
+
position: "relative",
|
|
364
|
+
// `backgroundColor` (not the `background` shorthand) so the
|
|
365
|
+
// CSS `background-image` set by `useSelectionController` can
|
|
366
|
+
// layer on top without clobbering this base colour.
|
|
367
|
+
backgroundColor: `var(${isLight ? chunkI4BQGFP5_cjs.CSS_VARS.SQ_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.SQ_DARK})`,
|
|
368
|
+
// Cursor is managed by `useCursorController` via injected CSS so
|
|
369
|
+
// it can flip between `pointer` / `grab` / `grabbing` without a
|
|
370
|
+
// React re-render per hover.
|
|
371
|
+
userSelect: "none",
|
|
372
|
+
// Suppress the browser's default focus ring; the keyboard focus
|
|
373
|
+
// indicator ships via `:focus-visible` (see `injectFocusRingStyles`).
|
|
374
|
+
outline: "none",
|
|
375
|
+
// Squares carry the container-query context so pieces inside can
|
|
376
|
+
// size relative to this cell (85cqh glyph sizing).
|
|
377
|
+
containerType: "size"
|
|
378
|
+
},
|
|
379
|
+
children: renderSquare !== void 0 ? renderSquare({ index, isLight, label }) : null
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
var Square = react.memo(SquareImpl);
|
|
384
|
+
Square.displayName = "Square";
|
|
385
|
+
function buildSquareOrder(orientation) {
|
|
386
|
+
const squares = [];
|
|
387
|
+
if (orientation === "white") {
|
|
388
|
+
for (let rank = 7; rank >= 0; rank--) {
|
|
389
|
+
for (let file = 0; file < 8; file++) {
|
|
390
|
+
squares.push(rank * 8 + file);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
for (let rank = 0; rank < 8; rank++) {
|
|
395
|
+
for (let file = 7; file >= 0; file--) {
|
|
396
|
+
squares.push(rank * 8 + file);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return squares;
|
|
401
|
+
}
|
|
402
|
+
var BoardGrid = react.memo(function BoardGrid2({
|
|
403
|
+
orientation,
|
|
404
|
+
onSquareClick,
|
|
405
|
+
renderSquare,
|
|
406
|
+
ariaLabel,
|
|
407
|
+
focusedSquare,
|
|
408
|
+
setSquareRef,
|
|
409
|
+
readOnly,
|
|
410
|
+
model,
|
|
411
|
+
getSquareAriaLabel,
|
|
412
|
+
onSquareFocus
|
|
413
|
+
}) {
|
|
414
|
+
const squares = react.useMemo(() => buildSquareOrder(orientation), [orientation]);
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
416
|
+
"div",
|
|
417
|
+
{
|
|
418
|
+
role: "grid",
|
|
419
|
+
"aria-label": ariaLabel ?? "Chess board",
|
|
420
|
+
"aria-rowcount": 8,
|
|
421
|
+
"aria-colcount": 8,
|
|
422
|
+
...readOnly === true ? { "aria-readonly": true } : {},
|
|
423
|
+
style: {
|
|
424
|
+
position: "absolute",
|
|
425
|
+
inset: 0,
|
|
426
|
+
display: "grid",
|
|
427
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
428
|
+
gridTemplateRows: "repeat(8, 1fr)"
|
|
429
|
+
},
|
|
430
|
+
children: squares.map((index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
431
|
+
Square,
|
|
432
|
+
{
|
|
433
|
+
index,
|
|
434
|
+
onClick: onSquareClick,
|
|
435
|
+
isFocused: focusedSquare === index,
|
|
436
|
+
model,
|
|
437
|
+
getSquareAriaLabel,
|
|
438
|
+
onFocus: onSquareFocus,
|
|
439
|
+
...renderSquare !== void 0 ? { renderSquare } : {},
|
|
440
|
+
...setSquareRef !== void 0 ? { setSquareRef } : {}
|
|
441
|
+
},
|
|
442
|
+
index
|
|
443
|
+
))
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
var WHITE_KING = 6;
|
|
448
|
+
var BLACK_KING = 12;
|
|
449
|
+
function findKing(board, turn) {
|
|
450
|
+
const code = turn === 0 ? WHITE_KING : BLACK_KING;
|
|
451
|
+
for (let i = 0; i < 64; i++) {
|
|
452
|
+
if (board[i] === code) return i;
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
function positionOf(index, orientation) {
|
|
457
|
+
const file = index & 7;
|
|
458
|
+
const rank = index >> 3;
|
|
459
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
460
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
461
|
+
return { x: col * 12.5, y: row * 12.5 };
|
|
462
|
+
}
|
|
463
|
+
function CheckLayer({ model, orientation }) {
|
|
464
|
+
const inCheck = useBoardSlice(model, (s) => s.inCheck);
|
|
465
|
+
if (!inCheck) return null;
|
|
466
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CheckLayerInner, { model, orientation });
|
|
467
|
+
}
|
|
468
|
+
function CheckLayerInner({ model, orientation }) {
|
|
469
|
+
const turn = useBoardSlice(model, (s) => s.turn);
|
|
470
|
+
const board = useBoardSlice(model, (s) => s.board);
|
|
471
|
+
const kingSquare = findKing(board, turn);
|
|
472
|
+
if (kingSquare === null) return null;
|
|
473
|
+
const { x, y } = positionOf(kingSquare, orientation);
|
|
474
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
475
|
+
"div",
|
|
476
|
+
{
|
|
477
|
+
"data-layer": "check",
|
|
478
|
+
"aria-hidden": "true",
|
|
479
|
+
style: {
|
|
480
|
+
position: "absolute",
|
|
481
|
+
left: `${x}%`,
|
|
482
|
+
top: `${y}%`,
|
|
483
|
+
width: "12.5%",
|
|
484
|
+
height: "12.5%",
|
|
485
|
+
background: `var(${chunkI4BQGFP5_cjs.CSS_VARS.CHECK})`,
|
|
486
|
+
pointerEvents: "none",
|
|
487
|
+
zIndex: 2
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
|
|
493
|
+
function Coordinates({ orientation, ranksPosition = "left" }) {
|
|
494
|
+
const ranksOnRight = ranksPosition === "right";
|
|
495
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
496
|
+
"div",
|
|
497
|
+
{
|
|
498
|
+
"aria-hidden": "true",
|
|
499
|
+
style: {
|
|
500
|
+
position: "absolute",
|
|
501
|
+
inset: 0,
|
|
502
|
+
pointerEvents: "none",
|
|
503
|
+
fontSize: "min(1.6vmin, 11px)",
|
|
504
|
+
fontWeight: 600,
|
|
505
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
506
|
+
},
|
|
507
|
+
children: [
|
|
508
|
+
FILE_LETTERS.map((letter, idx) => {
|
|
509
|
+
const col = orientation === "white" ? idx : 7 - idx;
|
|
510
|
+
const bottomRank = orientation === "white" ? 0 : 7;
|
|
511
|
+
const isLightSquare = (col + bottomRank) % 2 === 1;
|
|
512
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
513
|
+
"span",
|
|
514
|
+
{
|
|
515
|
+
style: {
|
|
516
|
+
position: "absolute",
|
|
517
|
+
left: `${col * 12.5 + 10}%`,
|
|
518
|
+
bottom: "1%",
|
|
519
|
+
color: `var(${isLightSquare ? chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_DARK})`
|
|
520
|
+
},
|
|
521
|
+
children: letter
|
|
522
|
+
},
|
|
523
|
+
`file-${letter}`
|
|
524
|
+
);
|
|
525
|
+
}),
|
|
526
|
+
FILE_LETTERS.map((_, idx) => {
|
|
527
|
+
const rank = idx;
|
|
528
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
529
|
+
const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
|
|
530
|
+
const isLightSquare = (edgeFile + rank) % 2 === 1;
|
|
531
|
+
const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
|
|
532
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
533
|
+
"span",
|
|
534
|
+
{
|
|
535
|
+
style: {
|
|
536
|
+
position: "absolute",
|
|
537
|
+
top: `${row * 12.5 + 1}%`,
|
|
538
|
+
...sideStyle,
|
|
539
|
+
color: `var(${isLightSquare ? chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_DARK})`
|
|
540
|
+
},
|
|
541
|
+
children: rank + 1
|
|
542
|
+
},
|
|
543
|
+
`rank-${rank}`
|
|
544
|
+
);
|
|
545
|
+
})
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
var WRAPPER_STYLE = Object.freeze({
|
|
551
|
+
position: "absolute",
|
|
552
|
+
top: 0,
|
|
553
|
+
left: 0,
|
|
554
|
+
width: "12.5%",
|
|
555
|
+
height: "12.5%",
|
|
556
|
+
pointerEvents: "none",
|
|
557
|
+
willChange: "transform",
|
|
558
|
+
zIndex: 20,
|
|
559
|
+
containerType: "size",
|
|
560
|
+
opacity: 0,
|
|
561
|
+
transform: "translate3d(0, 0, 0)"
|
|
562
|
+
});
|
|
563
|
+
var SLOT_STYLE = Object.freeze({
|
|
564
|
+
position: "absolute",
|
|
565
|
+
inset: 0,
|
|
566
|
+
opacity: 0,
|
|
567
|
+
containerType: "size"
|
|
568
|
+
});
|
|
569
|
+
var DragLayer = react.forwardRef(function DragLayer2({ pieces }, ref) {
|
|
570
|
+
const wrapperRef = react.useRef(null);
|
|
571
|
+
const slotRefs = react.useRef(new Array(13).fill(null));
|
|
572
|
+
const slots = react.useMemo(() => {
|
|
573
|
+
const nodes = [];
|
|
574
|
+
for (let cell = 1; cell <= 12; cell++) {
|
|
575
|
+
const child = pieces({ cell, square: 0 });
|
|
576
|
+
nodes.push(
|
|
577
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
578
|
+
"div",
|
|
579
|
+
{
|
|
580
|
+
ref: (el) => {
|
|
581
|
+
slotRefs.current[cell] = el;
|
|
582
|
+
},
|
|
583
|
+
"data-drag-cell": cell,
|
|
584
|
+
style: SLOT_STYLE,
|
|
585
|
+
children: child
|
|
586
|
+
},
|
|
587
|
+
cell
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
return nodes;
|
|
592
|
+
}, [pieces]);
|
|
593
|
+
react.useImperativeHandle(
|
|
594
|
+
ref,
|
|
595
|
+
() => ({
|
|
596
|
+
show(cell) {
|
|
597
|
+
const wrapper = wrapperRef.current;
|
|
598
|
+
if (wrapper === null) return;
|
|
599
|
+
wrapper.style.opacity = "1";
|
|
600
|
+
wrapper.dataset["active"] = "true";
|
|
601
|
+
for (let c = 1; c <= 12; c++) {
|
|
602
|
+
const el = slotRefs.current[c];
|
|
603
|
+
if (el !== null && el !== void 0) {
|
|
604
|
+
el.style.opacity = c === cell ? "1" : "0";
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
hide() {
|
|
609
|
+
const wrapper = wrapperRef.current;
|
|
610
|
+
if (wrapper === null) return;
|
|
611
|
+
wrapper.style.opacity = "0";
|
|
612
|
+
wrapper.dataset["active"] = "false";
|
|
613
|
+
},
|
|
614
|
+
setTransform(x, y) {
|
|
615
|
+
const wrapper = wrapperRef.current;
|
|
616
|
+
if (wrapper === null) return;
|
|
617
|
+
wrapper.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
618
|
+
},
|
|
619
|
+
element() {
|
|
620
|
+
return wrapperRef.current;
|
|
621
|
+
}
|
|
622
|
+
}),
|
|
623
|
+
[]
|
|
624
|
+
);
|
|
625
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
626
|
+
"div",
|
|
627
|
+
{
|
|
628
|
+
ref: wrapperRef,
|
|
629
|
+
"data-layer": "drag",
|
|
630
|
+
"data-active": "false",
|
|
631
|
+
"aria-hidden": "true",
|
|
632
|
+
style: WRAPPER_STYLE,
|
|
633
|
+
children: slots
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
});
|
|
637
|
+
function positionOf2(index, orientation) {
|
|
638
|
+
const file = index & 7;
|
|
639
|
+
const rank = index >> 3;
|
|
640
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
641
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
642
|
+
return { x: col * 12.5, y: row * 12.5 };
|
|
643
|
+
}
|
|
644
|
+
function prefersReducedMotion() {
|
|
645
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
649
|
+
}
|
|
650
|
+
var FLASH_DURATION_MS = 280;
|
|
651
|
+
function IllegalFlashLayer({ at, orientation }) {
|
|
652
|
+
const elRef = react.useRef(null);
|
|
653
|
+
react.useLayoutEffect(() => {
|
|
654
|
+
if (at === null) return;
|
|
655
|
+
const el = elRef.current;
|
|
656
|
+
if (el === null || typeof el.animate !== "function") return;
|
|
657
|
+
const duration = prefersReducedMotion() ? 0 : FLASH_DURATION_MS;
|
|
658
|
+
el.animate(
|
|
659
|
+
[
|
|
660
|
+
{ opacity: 0, offset: 0 },
|
|
661
|
+
{ opacity: 1, offset: 0.15 },
|
|
662
|
+
{ opacity: 0, offset: 1 }
|
|
663
|
+
],
|
|
664
|
+
{ duration, easing: "ease-out", fill: "forwards" }
|
|
665
|
+
);
|
|
666
|
+
}, [at]);
|
|
667
|
+
if (at === null) return null;
|
|
668
|
+
const { x, y } = positionOf2(at, orientation);
|
|
669
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
670
|
+
"div",
|
|
671
|
+
{
|
|
672
|
+
ref: elRef,
|
|
673
|
+
"data-layer": "illegal-flash",
|
|
674
|
+
"aria-hidden": "true",
|
|
675
|
+
style: {
|
|
676
|
+
position: "absolute",
|
|
677
|
+
left: `${x}%`,
|
|
678
|
+
top: `${y}%`,
|
|
679
|
+
width: "12.5%",
|
|
680
|
+
height: "12.5%",
|
|
681
|
+
background: chunkI4BQGFP5_cjs.DEFAULT_ILLEGAL_FLASH_TINT,
|
|
682
|
+
opacity: 0,
|
|
683
|
+
pointerEvents: "none",
|
|
684
|
+
zIndex: 5
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
function algebraicOf2(index) {
|
|
690
|
+
const file = index & 7;
|
|
691
|
+
const rank = index >> 3;
|
|
692
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
693
|
+
}
|
|
694
|
+
function pieceName(cell) {
|
|
695
|
+
if (cell === 0) return "piece";
|
|
696
|
+
const color = cell > 6 ? "black" : "white";
|
|
697
|
+
const type = (cell - 1) % 6;
|
|
698
|
+
switch (type) {
|
|
699
|
+
case 0:
|
|
700
|
+
return `${color} pawn`;
|
|
701
|
+
case 1:
|
|
702
|
+
return `${color} knight`;
|
|
703
|
+
case 2:
|
|
704
|
+
return `${color} bishop`;
|
|
705
|
+
case 3:
|
|
706
|
+
return `${color} rook`;
|
|
707
|
+
case 4:
|
|
708
|
+
return `${color} queen`;
|
|
709
|
+
case 5:
|
|
710
|
+
return `${color} king`;
|
|
711
|
+
default:
|
|
712
|
+
return `${color} piece`;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function announcementFor(lastMove, board, wasCapture) {
|
|
716
|
+
const { from, to, kind, promotion } = chunkUUTEFNFO_cjs.decodePackedMove(lastMove);
|
|
717
|
+
if (kind === "castle") {
|
|
718
|
+
const isKingside = (to & 7) > (from & 7);
|
|
719
|
+
return isKingside ? "Castles kingside." : "Castles queenside.";
|
|
720
|
+
}
|
|
721
|
+
const landed = board[to] ?? 0;
|
|
722
|
+
const name = pieceName(landed);
|
|
723
|
+
const fromSq = algebraicOf2(from);
|
|
724
|
+
const toSq = algebraicOf2(to);
|
|
725
|
+
if (kind === "promotion" && promotion !== null) {
|
|
726
|
+
const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
|
|
727
|
+
const action = wasCapture ? "captures and promotes" : "promotes";
|
|
728
|
+
return `Pawn ${fromSq} ${action} to ${promoted} on ${toSq}.`;
|
|
729
|
+
}
|
|
730
|
+
if (kind === "en-passant") {
|
|
731
|
+
return `${capitalise(name)} ${fromSq} captures en passant on ${toSq}.`;
|
|
732
|
+
}
|
|
733
|
+
if (wasCapture) {
|
|
734
|
+
return `${capitalise(name)} ${fromSq} captures on ${toSq}.`;
|
|
735
|
+
}
|
|
736
|
+
return `${capitalise(name)} ${fromSq} to ${toSq}.`;
|
|
737
|
+
}
|
|
738
|
+
function capitalise(s) {
|
|
739
|
+
return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
|
|
740
|
+
}
|
|
741
|
+
var srOnlyStyle = {
|
|
742
|
+
position: "absolute",
|
|
743
|
+
width: "1px",
|
|
744
|
+
height: "1px",
|
|
745
|
+
padding: 0,
|
|
746
|
+
margin: "-1px",
|
|
747
|
+
overflow: "hidden",
|
|
748
|
+
clip: "rect(0, 0, 0, 0)",
|
|
749
|
+
whiteSpace: "nowrap",
|
|
750
|
+
border: 0
|
|
751
|
+
};
|
|
752
|
+
function LiveRegion({ model }) {
|
|
753
|
+
const lastMove = useBoardSlice(model, (s) => s.lastMove);
|
|
754
|
+
const message = react.useMemo(() => {
|
|
755
|
+
if (lastMove === null) return "";
|
|
756
|
+
const wasCapture = model.lastAnimations.some(
|
|
757
|
+
(d) => d.kind === "capture" || d.kind === "en-passant"
|
|
758
|
+
);
|
|
759
|
+
return announcementFor(lastMove, model.getSnapshot().board, wasCapture);
|
|
760
|
+
}, [lastMove, model]);
|
|
761
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
762
|
+
"div",
|
|
763
|
+
{
|
|
764
|
+
role: "status",
|
|
765
|
+
"aria-live": "polite",
|
|
766
|
+
"aria-atomic": "true",
|
|
767
|
+
"data-layer": "live-region",
|
|
768
|
+
style: srOnlyStyle,
|
|
769
|
+
children: message
|
|
770
|
+
}
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
function gridCoord(index, orientation) {
|
|
774
|
+
const file = index & 7;
|
|
775
|
+
const rank = index >> 3;
|
|
776
|
+
const col = orientation === "white" ? file + 1 : 8 - file;
|
|
777
|
+
const row = orientation === "white" ? 8 - rank : rank + 1;
|
|
778
|
+
return { col, row };
|
|
779
|
+
}
|
|
780
|
+
function algebraicOf3(index) {
|
|
781
|
+
const file = index & 7;
|
|
782
|
+
const rank = index >> 3;
|
|
783
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
784
|
+
}
|
|
785
|
+
var SLOT_BASE_STYLE = Object.freeze({
|
|
786
|
+
display: "flex",
|
|
787
|
+
alignItems: "center",
|
|
788
|
+
justifyContent: "center",
|
|
789
|
+
pointerEvents: "none",
|
|
790
|
+
containerType: "size"
|
|
791
|
+
});
|
|
792
|
+
function PieceSlotImpl({ index, model, orientation, pieces }) {
|
|
793
|
+
const cell = useSquareCell(model, index);
|
|
794
|
+
if (cell === 0) return null;
|
|
795
|
+
const { col, row } = gridCoord(index, orientation);
|
|
796
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
797
|
+
"div",
|
|
798
|
+
{
|
|
799
|
+
"data-piece-square": algebraicOf3(index),
|
|
800
|
+
"data-piece-cell": cell,
|
|
801
|
+
style: { ...SLOT_BASE_STYLE, gridColumnStart: col, gridRowStart: row },
|
|
802
|
+
children: pieces({ cell, square: index })
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
var PieceSlot = react.memo(PieceSlotImpl);
|
|
807
|
+
PieceSlot.displayName = "PieceSlot";
|
|
808
|
+
var CONTAINER_STYLE = Object.freeze({
|
|
809
|
+
position: "absolute",
|
|
810
|
+
inset: 0,
|
|
811
|
+
display: "grid",
|
|
812
|
+
gridTemplateColumns: "repeat(8, 1fr)",
|
|
813
|
+
gridTemplateRows: "repeat(8, 1fr)",
|
|
814
|
+
pointerEvents: "none"
|
|
815
|
+
});
|
|
816
|
+
var PieceLayer = react.memo(function PieceLayer2({
|
|
817
|
+
model,
|
|
818
|
+
orientation,
|
|
819
|
+
pieces
|
|
820
|
+
}) {
|
|
821
|
+
const slots = [];
|
|
822
|
+
for (let i = 0; i < 64; i++) {
|
|
823
|
+
slots.push(
|
|
824
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
825
|
+
PieceSlot,
|
|
826
|
+
{
|
|
827
|
+
index: i,
|
|
828
|
+
model,
|
|
829
|
+
orientation,
|
|
830
|
+
pieces
|
|
831
|
+
},
|
|
832
|
+
i
|
|
833
|
+
)
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
|
|
837
|
+
});
|
|
838
|
+
function positionOf3(index, orientation) {
|
|
839
|
+
const file = index & 7;
|
|
840
|
+
const rank = index >> 3;
|
|
841
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
842
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
843
|
+
return { x: col * 12.5, y: row * 12.5 };
|
|
844
|
+
}
|
|
845
|
+
function PremoveLayer({ model, orientation, pieces }) {
|
|
846
|
+
const premoves = useBoardSlice(model, (s) => s.premoves);
|
|
847
|
+
if (premoves.length === 0) return null;
|
|
848
|
+
const head = premoves[0];
|
|
849
|
+
if (head === void 0) return null;
|
|
850
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
851
|
+
PremoveLayerInner,
|
|
852
|
+
{
|
|
853
|
+
model,
|
|
854
|
+
orientation,
|
|
855
|
+
pieces,
|
|
856
|
+
from: head.from,
|
|
857
|
+
to: head.to
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
function PremoveLayerInner({ model, orientation, pieces, from, to }) {
|
|
862
|
+
const cell = useSquareCell(model, from);
|
|
863
|
+
if (cell === 0) return null;
|
|
864
|
+
const origin = positionOf3(from, orientation);
|
|
865
|
+
const target = positionOf3(to, orientation);
|
|
866
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
867
|
+
"div",
|
|
868
|
+
{
|
|
869
|
+
"data-layer": "premove",
|
|
870
|
+
"aria-hidden": "true",
|
|
871
|
+
style: {
|
|
872
|
+
position: "absolute",
|
|
873
|
+
inset: 0,
|
|
874
|
+
pointerEvents: "none",
|
|
875
|
+
zIndex: 4
|
|
876
|
+
},
|
|
877
|
+
children: [
|
|
878
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
879
|
+
"div",
|
|
880
|
+
{
|
|
881
|
+
style: {
|
|
882
|
+
position: "absolute",
|
|
883
|
+
left: `${origin.x}%`,
|
|
884
|
+
top: `${origin.y}%`,
|
|
885
|
+
width: "12.5%",
|
|
886
|
+
height: "12.5%",
|
|
887
|
+
background: chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT,
|
|
888
|
+
opacity: 0.5
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
893
|
+
"div",
|
|
894
|
+
{
|
|
895
|
+
style: {
|
|
896
|
+
position: "absolute",
|
|
897
|
+
left: `${target.x}%`,
|
|
898
|
+
top: `${target.y}%`,
|
|
899
|
+
width: "12.5%",
|
|
900
|
+
height: "12.5%",
|
|
901
|
+
background: chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
),
|
|
905
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
906
|
+
"div",
|
|
907
|
+
{
|
|
908
|
+
"data-premove-ghost": "true",
|
|
909
|
+
style: {
|
|
910
|
+
position: "absolute",
|
|
911
|
+
left: `${target.x}%`,
|
|
912
|
+
top: `${target.y}%`,
|
|
913
|
+
width: "12.5%",
|
|
914
|
+
height: "12.5%",
|
|
915
|
+
display: "flex",
|
|
916
|
+
alignItems: "center",
|
|
917
|
+
justifyContent: "center",
|
|
918
|
+
opacity: 0.55,
|
|
919
|
+
containerType: "size"
|
|
920
|
+
},
|
|
921
|
+
children: pieces({ cell, square: to })
|
|
922
|
+
}
|
|
923
|
+
)
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
function positionOf4(index, orientation) {
|
|
929
|
+
const file = index & 7;
|
|
930
|
+
const rank = index >> 3;
|
|
931
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
932
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
933
|
+
return { x: col * 12.5, y: row * 12.5 };
|
|
934
|
+
}
|
|
935
|
+
var CHOICE_ORDER = [
|
|
936
|
+
chunkUUTEFNFO_cjs.PieceType.Queen,
|
|
937
|
+
chunkUUTEFNFO_cjs.PieceType.Knight,
|
|
938
|
+
chunkUUTEFNFO_cjs.PieceType.Rook,
|
|
939
|
+
chunkUUTEFNFO_cjs.PieceType.Bishop
|
|
940
|
+
];
|
|
941
|
+
var SHORTCUTS = {
|
|
942
|
+
q: chunkUUTEFNFO_cjs.PieceType.Queen,
|
|
943
|
+
r: chunkUUTEFNFO_cjs.PieceType.Rook,
|
|
944
|
+
b: chunkUUTEFNFO_cjs.PieceType.Bishop,
|
|
945
|
+
n: chunkUUTEFNFO_cjs.PieceType.Knight,
|
|
946
|
+
Q: chunkUUTEFNFO_cjs.PieceType.Queen,
|
|
947
|
+
R: chunkUUTEFNFO_cjs.PieceType.Rook,
|
|
948
|
+
B: chunkUUTEFNFO_cjs.PieceType.Bishop,
|
|
949
|
+
N: chunkUUTEFNFO_cjs.PieceType.Knight
|
|
950
|
+
};
|
|
951
|
+
function PromotionOverlay({
|
|
952
|
+
from: _from,
|
|
953
|
+
to,
|
|
954
|
+
color,
|
|
955
|
+
orientation,
|
|
956
|
+
pieces,
|
|
957
|
+
onSelect,
|
|
958
|
+
onCancel,
|
|
959
|
+
getPromotionPieceAriaLabel
|
|
960
|
+
}) {
|
|
961
|
+
const buttonRefs = react.useRef([]);
|
|
962
|
+
const getLabel = react.useCallback(
|
|
963
|
+
(piece) => getPromotionPieceAriaLabel ? getPromotionPieceAriaLabel(piece, color) : pieceName2(piece),
|
|
964
|
+
[getPromotionPieceAriaLabel, color]
|
|
965
|
+
);
|
|
966
|
+
const [activeAnnouncement, setActiveAnnouncement] = react.useState(
|
|
967
|
+
() => getLabel(CHOICE_ORDER[0] ?? chunkUUTEFNFO_cjs.PieceType.Queen)
|
|
968
|
+
);
|
|
969
|
+
react.useEffect(() => {
|
|
970
|
+
buttonRefs.current[0]?.focus();
|
|
971
|
+
}, []);
|
|
972
|
+
react.useEffect(() => {
|
|
973
|
+
const onKey = (e) => {
|
|
974
|
+
if (e.key === "Escape") {
|
|
975
|
+
e.preventDefault();
|
|
976
|
+
onCancel();
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (e.key === "Tab") {
|
|
980
|
+
e.preventDefault();
|
|
981
|
+
const activeEl = document.activeElement;
|
|
982
|
+
const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
|
|
983
|
+
let nextIdx;
|
|
984
|
+
if (e.shiftKey) {
|
|
985
|
+
nextIdx = currentIdx <= 0 ? buttonRefs.current.length - 1 : currentIdx - 1;
|
|
986
|
+
} else {
|
|
987
|
+
nextIdx = currentIdx >= buttonRefs.current.length - 1 ? 0 : currentIdx + 1;
|
|
988
|
+
}
|
|
989
|
+
buttonRefs.current[nextIdx]?.focus();
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
|
|
993
|
+
e.preventDefault();
|
|
994
|
+
const activeEl = document.activeElement;
|
|
995
|
+
const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
|
|
996
|
+
const nextIdx = currentIdx < 0 ? 0 : (currentIdx + 1) % CHOICE_ORDER.length;
|
|
997
|
+
buttonRefs.current[nextIdx]?.focus();
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
|
|
1001
|
+
e.preventDefault();
|
|
1002
|
+
const activeEl = document.activeElement;
|
|
1003
|
+
const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
|
|
1004
|
+
const prevIdx = currentIdx <= 0 ? CHOICE_ORDER.length - 1 : currentIdx - 1;
|
|
1005
|
+
buttonRefs.current[prevIdx]?.focus();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (e.key === "Home") {
|
|
1009
|
+
e.preventDefault();
|
|
1010
|
+
buttonRefs.current[0]?.focus();
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
if (e.key === "End") {
|
|
1014
|
+
e.preventDefault();
|
|
1015
|
+
buttonRefs.current[CHOICE_ORDER.length - 1]?.focus();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const choice = SHORTCUTS[e.key];
|
|
1019
|
+
if (choice !== void 0) {
|
|
1020
|
+
e.preventDefault();
|
|
1021
|
+
onSelect(choice);
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
window.addEventListener("keydown", onKey);
|
|
1025
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1026
|
+
}, [onSelect, onCancel]);
|
|
1027
|
+
const targetPos = positionOf4(to, orientation);
|
|
1028
|
+
const promotingColorIsBlack = color === 1;
|
|
1029
|
+
const stackUpward = orientation === "white" ? promotingColorIsBlack : !promotingColorIsBlack;
|
|
1030
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1031
|
+
"div",
|
|
1032
|
+
{
|
|
1033
|
+
role: "presentation",
|
|
1034
|
+
"data-layer": "promotion-overlay",
|
|
1035
|
+
onClick: onCancel,
|
|
1036
|
+
style: {
|
|
1037
|
+
position: "absolute",
|
|
1038
|
+
inset: 0,
|
|
1039
|
+
background: "rgba(0, 0, 0, 0.35)",
|
|
1040
|
+
zIndex: 30,
|
|
1041
|
+
cursor: "pointer"
|
|
1042
|
+
},
|
|
1043
|
+
children: [
|
|
1044
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1045
|
+
"div",
|
|
1046
|
+
{
|
|
1047
|
+
role: "dialog",
|
|
1048
|
+
"aria-modal": "true",
|
|
1049
|
+
"aria-label": `Choose promotion piece at ${algebraicOf4(to)}`,
|
|
1050
|
+
onClick: (e) => e.stopPropagation(),
|
|
1051
|
+
style: {
|
|
1052
|
+
position: "absolute",
|
|
1053
|
+
left: `${targetPos.x}%`,
|
|
1054
|
+
top: `${stackUpward ? targetPos.y - 12.5 * 3 : targetPos.y}%`,
|
|
1055
|
+
width: "12.5%",
|
|
1056
|
+
height: `${12.5 * 4}%`,
|
|
1057
|
+
display: "flex",
|
|
1058
|
+
flexDirection: stackUpward ? "column-reverse" : "column",
|
|
1059
|
+
background: "#ffffff",
|
|
1060
|
+
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.28)",
|
|
1061
|
+
borderRadius: "4px",
|
|
1062
|
+
overflow: "hidden"
|
|
1063
|
+
},
|
|
1064
|
+
children: CHOICE_ORDER.map((piece, index) => {
|
|
1065
|
+
const cell = chunkUUTEFNFO_cjs.encodeBoardCell(color, piece);
|
|
1066
|
+
const label = getLabel(piece);
|
|
1067
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1068
|
+
"button",
|
|
1069
|
+
{
|
|
1070
|
+
ref: (el) => {
|
|
1071
|
+
buttonRefs.current[index] = el;
|
|
1072
|
+
},
|
|
1073
|
+
type: "button",
|
|
1074
|
+
onClick: () => onSelect(piece),
|
|
1075
|
+
onFocus: () => setActiveAnnouncement(label),
|
|
1076
|
+
"aria-label": label,
|
|
1077
|
+
style: {
|
|
1078
|
+
flex: 1,
|
|
1079
|
+
display: "flex",
|
|
1080
|
+
alignItems: "center",
|
|
1081
|
+
justifyContent: "center",
|
|
1082
|
+
border: "none",
|
|
1083
|
+
background: "transparent",
|
|
1084
|
+
cursor: "pointer",
|
|
1085
|
+
padding: 0,
|
|
1086
|
+
margin: 0,
|
|
1087
|
+
font: "inherit",
|
|
1088
|
+
containerType: "size"
|
|
1089
|
+
},
|
|
1090
|
+
"data-promotion-piece": pieceName2(piece),
|
|
1091
|
+
children: pieces({ cell, square: to })
|
|
1092
|
+
},
|
|
1093
|
+
piece
|
|
1094
|
+
);
|
|
1095
|
+
})
|
|
1096
|
+
}
|
|
1097
|
+
),
|
|
1098
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", style: srOnlyStyle2, children: activeAnnouncement }),
|
|
1099
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", style: srOnlyStyle2, children: "Choose a promotion piece with Q, N, R, or B, or arrow keys. Press Escape to cancel." })
|
|
1100
|
+
]
|
|
1101
|
+
}
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
var srOnlyStyle2 = {
|
|
1105
|
+
position: "absolute",
|
|
1106
|
+
width: "1px",
|
|
1107
|
+
height: "1px",
|
|
1108
|
+
padding: 0,
|
|
1109
|
+
margin: "-1px",
|
|
1110
|
+
overflow: "hidden",
|
|
1111
|
+
clip: "rect(0, 0, 0, 0)",
|
|
1112
|
+
whiteSpace: "nowrap",
|
|
1113
|
+
border: 0
|
|
1114
|
+
};
|
|
1115
|
+
function pieceName2(t) {
|
|
1116
|
+
switch (t) {
|
|
1117
|
+
case chunkUUTEFNFO_cjs.PieceType.Queen:
|
|
1118
|
+
return "Queen";
|
|
1119
|
+
case chunkUUTEFNFO_cjs.PieceType.Rook:
|
|
1120
|
+
return "Rook";
|
|
1121
|
+
case chunkUUTEFNFO_cjs.PieceType.Bishop:
|
|
1122
|
+
return "Bishop";
|
|
1123
|
+
case chunkUUTEFNFO_cjs.PieceType.Knight:
|
|
1124
|
+
return "Knight";
|
|
1125
|
+
default:
|
|
1126
|
+
return "Piece";
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function algebraicOf4(index) {
|
|
1130
|
+
const file = index & 7;
|
|
1131
|
+
const rank = index >> 3;
|
|
1132
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1133
|
+
}
|
|
1134
|
+
function StaticPieceLayer({ board, orientation, pieces }) {
|
|
1135
|
+
const slots = [];
|
|
1136
|
+
for (let i = 0; i < 64; i++) {
|
|
1137
|
+
const cell = board[i] ?? 0;
|
|
1138
|
+
if (cell === 0) continue;
|
|
1139
|
+
const index = i;
|
|
1140
|
+
const { col, row } = gridCoord(index, orientation);
|
|
1141
|
+
slots.push(
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1143
|
+
"div",
|
|
1144
|
+
{
|
|
1145
|
+
"data-piece-square": algebraicOf3(index),
|
|
1146
|
+
"data-piece-cell": cell,
|
|
1147
|
+
style: { ...SLOT_BASE_STYLE, gridColumnStart: col, gridRowStart: row },
|
|
1148
|
+
children: pieces({ cell, square: index })
|
|
1149
|
+
},
|
|
1150
|
+
i
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
|
|
1155
|
+
}
|
|
1156
|
+
var DEFAULT_DURATION_MS = 60;
|
|
1157
|
+
var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
1158
|
+
function algebraicOf5(index) {
|
|
1159
|
+
const file = index & 7;
|
|
1160
|
+
const rank = index >> 3;
|
|
1161
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1162
|
+
}
|
|
1163
|
+
function deltaFromTo(from, to, squareSize, orientation) {
|
|
1164
|
+
const fromFile = from & 7;
|
|
1165
|
+
const fromRank = from >> 3;
|
|
1166
|
+
const toFile = to & 7;
|
|
1167
|
+
const toRank = to >> 3;
|
|
1168
|
+
const fromCol = orientation === "white" ? fromFile : 7 - fromFile;
|
|
1169
|
+
const fromRow = orientation === "white" ? 7 - fromRank : fromRank;
|
|
1170
|
+
const toCol = orientation === "white" ? toFile : 7 - toFile;
|
|
1171
|
+
const toRow = orientation === "white" ? 7 - toRank : toRank;
|
|
1172
|
+
return {
|
|
1173
|
+
dx: (fromCol - toCol) * squareSize,
|
|
1174
|
+
dy: (fromRow - toRow) * squareSize
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
function cancelAnimations(element) {
|
|
1178
|
+
if (typeof element.getAnimations !== "function") return;
|
|
1179
|
+
for (const anim of element.getAnimations()) anim.cancel();
|
|
1180
|
+
}
|
|
1181
|
+
function prefersReducedMotion2() {
|
|
1182
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1186
|
+
}
|
|
1187
|
+
function animateGlide(element, dx, dy, duration, easing) {
|
|
1188
|
+
cancelAnimations(element);
|
|
1189
|
+
if (duration <= 0 || typeof element.animate !== "function") return;
|
|
1190
|
+
element.animate(
|
|
1191
|
+
[{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "translate(0, 0)" }],
|
|
1192
|
+
{ duration, easing, fill: "none" }
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
function runAnimations(descriptors, container, orientation, duration, easing) {
|
|
1196
|
+
const rect = container.getBoundingClientRect();
|
|
1197
|
+
const sqSize = rect.width / 8;
|
|
1198
|
+
if (sqSize <= 0) return;
|
|
1199
|
+
for (const d of descriptors) {
|
|
1200
|
+
switch (d.kind) {
|
|
1201
|
+
case "move":
|
|
1202
|
+
case "capture":
|
|
1203
|
+
case "en-passant":
|
|
1204
|
+
case "promotion": {
|
|
1205
|
+
const toEl = container.querySelector(
|
|
1206
|
+
`[data-piece-square="${algebraicOf5(d.to)}"]`
|
|
1207
|
+
);
|
|
1208
|
+
if (toEl === null) break;
|
|
1209
|
+
const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
|
|
1210
|
+
animateGlide(toEl, dx, dy, duration, easing);
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
case "castle": {
|
|
1214
|
+
const kingEl = container.querySelector(
|
|
1215
|
+
`[data-piece-square="${algebraicOf5(d.kingTo)}"]`
|
|
1216
|
+
);
|
|
1217
|
+
const rookEl = container.querySelector(
|
|
1218
|
+
`[data-piece-square="${algebraicOf5(d.rookTo)}"]`
|
|
1219
|
+
);
|
|
1220
|
+
if (kingEl !== null) {
|
|
1221
|
+
const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
|
|
1222
|
+
animateGlide(kingEl, dx, dy, duration, easing);
|
|
1223
|
+
}
|
|
1224
|
+
if (rookEl !== null) {
|
|
1225
|
+
const { dx, dy } = deltaFromTo(d.rookFrom, d.rookTo, sqSize, orientation);
|
|
1226
|
+
animateGlide(rookEl, dx, dy, duration, easing);
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
function AnimationRunner({
|
|
1234
|
+
model,
|
|
1235
|
+
containerRef,
|
|
1236
|
+
orientation,
|
|
1237
|
+
options,
|
|
1238
|
+
runtime
|
|
1239
|
+
}) {
|
|
1240
|
+
const duration = options?.durationMs ?? DEFAULT_DURATION_MS;
|
|
1241
|
+
const easing = options?.easing ?? DEFAULT_EASING;
|
|
1242
|
+
const skipNextRef = runtime?.skipNextRef;
|
|
1243
|
+
const historyPly = useBoardSlice(model, (s) => s.historyPly);
|
|
1244
|
+
const lastAnimatedPlyRef = react.useRef(null);
|
|
1245
|
+
react.useLayoutEffect(() => {
|
|
1246
|
+
if (lastAnimatedPlyRef.current === historyPly) return;
|
|
1247
|
+
lastAnimatedPlyRef.current = historyPly;
|
|
1248
|
+
const container = containerRef.current;
|
|
1249
|
+
if (container === null) return;
|
|
1250
|
+
const descriptors = model.lastAnimations;
|
|
1251
|
+
if (descriptors.length === 0) return;
|
|
1252
|
+
if (skipNextRef?.current) {
|
|
1253
|
+
skipNextRef.current = false;
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const effectiveDuration = prefersReducedMotion2() ? 0 : duration;
|
|
1257
|
+
if (effectiveDuration <= 0) return;
|
|
1258
|
+
runAnimations(descriptors, container, orientation, effectiveDuration, easing);
|
|
1259
|
+
}, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/lib/geometry.ts
|
|
1264
|
+
function getSquareAtPoint(container, clientX, clientY, orientation) {
|
|
1265
|
+
const rect = container.getBoundingClientRect();
|
|
1266
|
+
const relX = clientX - rect.left;
|
|
1267
|
+
const relY = clientY - rect.top;
|
|
1268
|
+
if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
|
|
1269
|
+
const col = Math.floor(relX / rect.width * 8);
|
|
1270
|
+
const row = Math.floor(relY / rect.height * 8);
|
|
1271
|
+
const file = orientation === "white" ? col : 7 - col;
|
|
1272
|
+
const rank = orientation === "white" ? 7 - row : row;
|
|
1273
|
+
return rank * 8 + file;
|
|
1274
|
+
}
|
|
1275
|
+
function getPointAtSquareCentre(container, square, orientation) {
|
|
1276
|
+
const rect = container.getBoundingClientRect();
|
|
1277
|
+
const sqW = rect.width / 8;
|
|
1278
|
+
const sqH = rect.height / 8;
|
|
1279
|
+
const file = square & 7;
|
|
1280
|
+
const rank = square >> 3;
|
|
1281
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
1282
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
1283
|
+
return {
|
|
1284
|
+
x: rect.left + sqW * col + sqW / 2,
|
|
1285
|
+
y: rect.top + sqH * row + sqH / 2
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function algebraicOf6(square) {
|
|
1289
|
+
const file = square & 7;
|
|
1290
|
+
const rank = square >> 3;
|
|
1291
|
+
return `${String.fromCharCode(97 + file)}${rank + 1}`;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// src/hooks/use-arrow-gesture.ts
|
|
1295
|
+
function colorForModifiers(e, palette) {
|
|
1296
|
+
if (e.shiftKey) return palette.shift;
|
|
1297
|
+
if (e.altKey) return palette.alt;
|
|
1298
|
+
if (e.ctrlKey || e.metaKey) return palette.ctrl;
|
|
1299
|
+
return palette.default;
|
|
1300
|
+
}
|
|
1301
|
+
function useArrowGesture(options) {
|
|
1302
|
+
const { game, orientation, containerRef, arrowsLayerRef, enabled, palette, snapToValidMove } = options;
|
|
1303
|
+
react.useEffect(() => {
|
|
1304
|
+
if (!enabled) return;
|
|
1305
|
+
const container = containerRef.current;
|
|
1306
|
+
if (container === null || game === null) return;
|
|
1307
|
+
let origin = null;
|
|
1308
|
+
let activePointerId = null;
|
|
1309
|
+
const clearPreview = () => {
|
|
1310
|
+
arrowsLayerRef.current?.setPreview(null);
|
|
1311
|
+
};
|
|
1312
|
+
const snap = (candidate) => {
|
|
1313
|
+
if (!snapToValidMove) return candidate;
|
|
1314
|
+
if (origin === null) return candidate;
|
|
1315
|
+
if (candidate === origin) return candidate;
|
|
1316
|
+
const legals = game.legalFrom(origin);
|
|
1317
|
+
if (legals.size === 0 || legals.has(candidate)) return candidate;
|
|
1318
|
+
const cf = candidate & 7;
|
|
1319
|
+
const cr = candidate >> 3;
|
|
1320
|
+
let best = candidate;
|
|
1321
|
+
let bestDist = Infinity;
|
|
1322
|
+
for (const t of legals) {
|
|
1323
|
+
const df = (t & 7) - cf;
|
|
1324
|
+
const dr = (t >> 3) - cr;
|
|
1325
|
+
const d = df * df + dr * dr;
|
|
1326
|
+
if (d < bestDist) {
|
|
1327
|
+
bestDist = d;
|
|
1328
|
+
best = t;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return best;
|
|
1332
|
+
};
|
|
1333
|
+
const setPreview = (to, color) => {
|
|
1334
|
+
if (origin === null) return;
|
|
1335
|
+
arrowsLayerRef.current?.setPreview({ from: origin, to: snap(to), color });
|
|
1336
|
+
};
|
|
1337
|
+
const onContextMenu = (e) => {
|
|
1338
|
+
e.preventDefault();
|
|
1339
|
+
};
|
|
1340
|
+
const onPointerDown = (e) => {
|
|
1341
|
+
if (e.button !== 2) return;
|
|
1342
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
1343
|
+
if (sq === null) return;
|
|
1344
|
+
origin = sq;
|
|
1345
|
+
activePointerId = e.pointerId;
|
|
1346
|
+
setPreview(sq, colorForModifiers(e, palette));
|
|
1347
|
+
try {
|
|
1348
|
+
container.setPointerCapture(e.pointerId);
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
const onPointerMove = (e) => {
|
|
1353
|
+
if (origin === null) return;
|
|
1354
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1355
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
1356
|
+
if (sq === null) {
|
|
1357
|
+
setPreview(origin, colorForModifiers(e, palette));
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
setPreview(sq, colorForModifiers(e, palette));
|
|
1361
|
+
};
|
|
1362
|
+
const onPointerUp = (e) => {
|
|
1363
|
+
if (origin === null) return;
|
|
1364
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1365
|
+
const raw = getSquareAtPoint(container, e.clientX, e.clientY, orientation) ?? origin;
|
|
1366
|
+
const target = snap(raw);
|
|
1367
|
+
const color = colorForModifiers(e, palette);
|
|
1368
|
+
game.toggleArrow({ from: origin, to: target, color });
|
|
1369
|
+
origin = null;
|
|
1370
|
+
activePointerId = null;
|
|
1371
|
+
clearPreview();
|
|
1372
|
+
};
|
|
1373
|
+
const onPointerCancel = (e) => {
|
|
1374
|
+
if (origin === null) return;
|
|
1375
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1376
|
+
origin = null;
|
|
1377
|
+
activePointerId = null;
|
|
1378
|
+
clearPreview();
|
|
1379
|
+
};
|
|
1380
|
+
container.addEventListener("contextmenu", onContextMenu);
|
|
1381
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
1382
|
+
container.addEventListener("pointermove", onPointerMove, { passive: true });
|
|
1383
|
+
container.addEventListener("pointerup", onPointerUp);
|
|
1384
|
+
container.addEventListener("pointercancel", onPointerCancel);
|
|
1385
|
+
return () => {
|
|
1386
|
+
container.removeEventListener("contextmenu", onContextMenu);
|
|
1387
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
1388
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
1389
|
+
container.removeEventListener("pointerup", onPointerUp);
|
|
1390
|
+
container.removeEventListener("pointercancel", onPointerCancel);
|
|
1391
|
+
clearPreview();
|
|
1392
|
+
};
|
|
1393
|
+
}, [game, orientation, containerRef, arrowsLayerRef, enabled, palette, snapToValidMove]);
|
|
1394
|
+
}
|
|
1395
|
+
function wouldPromote(fromCell, toIndex) {
|
|
1396
|
+
if (fromCell === 0) return false;
|
|
1397
|
+
const pieceType = (fromCell - 1) % 6;
|
|
1398
|
+
if (pieceType !== chunkUUTEFNFO_cjs.PieceType.Pawn) return false;
|
|
1399
|
+
const toRank = toIndex >> 3;
|
|
1400
|
+
return toRank === 0 || toRank === 7;
|
|
1401
|
+
}
|
|
1402
|
+
function useClickToMove(model, onMove, onPromotionNeeded, enabled = true) {
|
|
1403
|
+
return react.useCallback(
|
|
1404
|
+
(index) => {
|
|
1405
|
+
if (!enabled) return;
|
|
1406
|
+
if (model === null) return;
|
|
1407
|
+
const snap = model.getSnapshot();
|
|
1408
|
+
const cell = snap.board[index] ?? 0;
|
|
1409
|
+
const pieceColour = cell === 0 ? -1 : cell > 6 ? chunkUUTEFNFO_cjs.Color.Black : chunkUUTEFNFO_cjs.Color.White;
|
|
1410
|
+
if (snap.selected === null) {
|
|
1411
|
+
if (pieceColour === snap.turn) model.selectSquare(index);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (snap.selected === index) {
|
|
1415
|
+
model.selectSquare(null);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (snap.legalTargets.has(index)) {
|
|
1419
|
+
const fromCell = snap.board[snap.selected] ?? 0;
|
|
1420
|
+
if (wouldPromote(fromCell, index)) {
|
|
1421
|
+
if (onPromotionNeeded !== void 0) {
|
|
1422
|
+
onPromotionNeeded(snap.selected, index);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const played2 = model.tryMove(snap.selected, index, chunkUUTEFNFO_cjs.PieceType.Queen);
|
|
1426
|
+
if (played2 !== null && onMove !== void 0) onMove(played2);
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
const played = model.tryMove(snap.selected, index);
|
|
1430
|
+
if (played !== null && onMove !== void 0) onMove(played);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (pieceColour === snap.turn) {
|
|
1434
|
+
model.selectSquare(index);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
model.selectSquare(null);
|
|
1438
|
+
},
|
|
1439
|
+
[model, onMove, onPromotionNeeded, enabled]
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
var STYLE_ID = "gb-cursor-styles";
|
|
1443
|
+
function injectCursorStyles() {
|
|
1444
|
+
if (typeof document === "undefined") return;
|
|
1445
|
+
if (document.getElementById(STYLE_ID) !== null) return;
|
|
1446
|
+
const style = document.createElement("style");
|
|
1447
|
+
style.id = STYLE_ID;
|
|
1448
|
+
style.textContent = `
|
|
1449
|
+
[data-gb-square] {
|
|
1450
|
+
cursor: pointer;
|
|
1451
|
+
}
|
|
1452
|
+
[data-gb-turn="white"] [data-piece-cell="1"],
|
|
1453
|
+
[data-gb-turn="white"] [data-piece-cell="2"],
|
|
1454
|
+
[data-gb-turn="white"] [data-piece-cell="3"],
|
|
1455
|
+
[data-gb-turn="white"] [data-piece-cell="4"],
|
|
1456
|
+
[data-gb-turn="white"] [data-piece-cell="5"],
|
|
1457
|
+
[data-gb-turn="white"] [data-piece-cell="6"],
|
|
1458
|
+
[data-gb-turn="black"] [data-piece-cell="7"],
|
|
1459
|
+
[data-gb-turn="black"] [data-piece-cell="8"],
|
|
1460
|
+
[data-gb-turn="black"] [data-piece-cell="9"],
|
|
1461
|
+
[data-gb-turn="black"] [data-piece-cell="10"],
|
|
1462
|
+
[data-gb-turn="black"] [data-piece-cell="11"],
|
|
1463
|
+
[data-gb-turn="black"] [data-piece-cell="12"] {
|
|
1464
|
+
pointer-events: auto;
|
|
1465
|
+
cursor: grab;
|
|
1466
|
+
}
|
|
1467
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="7"],
|
|
1468
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="8"],
|
|
1469
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="9"],
|
|
1470
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="10"],
|
|
1471
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="11"],
|
|
1472
|
+
[data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="12"],
|
|
1473
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="1"],
|
|
1474
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="2"],
|
|
1475
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="3"],
|
|
1476
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="4"],
|
|
1477
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="5"],
|
|
1478
|
+
[data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="6"] {
|
|
1479
|
+
pointer-events: auto;
|
|
1480
|
+
cursor: grab;
|
|
1481
|
+
}
|
|
1482
|
+
[data-gb-dragging="true"] [data-gb-square],
|
|
1483
|
+
[data-gb-dragging="true"] [data-piece-cell] {
|
|
1484
|
+
cursor: grabbing;
|
|
1485
|
+
}
|
|
1486
|
+
`;
|
|
1487
|
+
document.head.appendChild(style);
|
|
1488
|
+
}
|
|
1489
|
+
function useCursorController(model, containerRef, allowDrag, allowPremove) {
|
|
1490
|
+
react.useEffect(() => {
|
|
1491
|
+
injectCursorStyles();
|
|
1492
|
+
}, []);
|
|
1493
|
+
react.useEffect(() => {
|
|
1494
|
+
if (model === null) return;
|
|
1495
|
+
let lastTurn = null;
|
|
1496
|
+
let lastPremove = null;
|
|
1497
|
+
const write = () => {
|
|
1498
|
+
const el = containerRef.current;
|
|
1499
|
+
if (el === null) return;
|
|
1500
|
+
if (!allowDrag) {
|
|
1501
|
+
if (lastTurn !== null) {
|
|
1502
|
+
delete el.dataset["gbTurn"];
|
|
1503
|
+
lastTurn = null;
|
|
1504
|
+
}
|
|
1505
|
+
if (lastPremove !== null) {
|
|
1506
|
+
delete el.dataset["gbPremove"];
|
|
1507
|
+
lastPremove = null;
|
|
1508
|
+
}
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
const turn = model.getSnapshot().turn === 0 ? "white" : "black";
|
|
1512
|
+
if (turn !== lastTurn) {
|
|
1513
|
+
el.dataset["gbTurn"] = turn;
|
|
1514
|
+
lastTurn = turn;
|
|
1515
|
+
}
|
|
1516
|
+
if (allowPremove !== lastPremove) {
|
|
1517
|
+
if (allowPremove) {
|
|
1518
|
+
el.dataset["gbPremove"] = "true";
|
|
1519
|
+
} else {
|
|
1520
|
+
delete el.dataset["gbPremove"];
|
|
1521
|
+
}
|
|
1522
|
+
lastPremove = allowPremove;
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
write();
|
|
1526
|
+
const unsubscribe = model.subscribe(write);
|
|
1527
|
+
return () => {
|
|
1528
|
+
unsubscribe();
|
|
1529
|
+
const el = containerRef.current;
|
|
1530
|
+
if (el !== null) {
|
|
1531
|
+
delete el.dataset["gbTurn"];
|
|
1532
|
+
delete el.dataset["gbPremove"];
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
}, [model, containerRef, allowDrag, allowPremove]);
|
|
1536
|
+
}
|
|
1537
|
+
var GHOST_OPACITY_VAR = chunkI4BQGFP5_cjs.CSS_VARS.DRAG_GHOST_OPACITY;
|
|
1538
|
+
function useDrag(options) {
|
|
1539
|
+
const {
|
|
1540
|
+
game,
|
|
1541
|
+
orientation,
|
|
1542
|
+
containerRef,
|
|
1543
|
+
dragLayerRef,
|
|
1544
|
+
enabled,
|
|
1545
|
+
activationDistance,
|
|
1546
|
+
onDragStart,
|
|
1547
|
+
onDragEnd,
|
|
1548
|
+
onDrop,
|
|
1549
|
+
canDragPiece
|
|
1550
|
+
} = options;
|
|
1551
|
+
const canDragPieceRef = react.useRef(canDragPiece);
|
|
1552
|
+
react.useEffect(() => {
|
|
1553
|
+
canDragPieceRef.current = canDragPiece;
|
|
1554
|
+
});
|
|
1555
|
+
react.useEffect(() => {
|
|
1556
|
+
if (!enabled) return;
|
|
1557
|
+
const container = containerRef.current;
|
|
1558
|
+
if (container === null || game === null) return;
|
|
1559
|
+
const controller = chunkUUTEFNFO_cjs.createDragController(
|
|
1560
|
+
activationDistance === void 0 ? {} : { activationDistance }
|
|
1561
|
+
);
|
|
1562
|
+
let originEl = null;
|
|
1563
|
+
let active = false;
|
|
1564
|
+
let activePointerId = null;
|
|
1565
|
+
const writeTransform = (clientX, clientY) => {
|
|
1566
|
+
const layer = dragLayerRef.current;
|
|
1567
|
+
if (layer === null) return;
|
|
1568
|
+
const rect = container.getBoundingClientRect();
|
|
1569
|
+
const sqSize = rect.width / 8;
|
|
1570
|
+
const x = clientX - rect.left - sqSize / 2;
|
|
1571
|
+
const y = clientY - rect.top - sqSize / 2;
|
|
1572
|
+
layer.setTransform(x, y);
|
|
1573
|
+
};
|
|
1574
|
+
const restoreOrigin = () => {
|
|
1575
|
+
if (originEl !== null) {
|
|
1576
|
+
originEl.style.opacity = "";
|
|
1577
|
+
originEl = null;
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
const cleanup = () => {
|
|
1581
|
+
restoreOrigin();
|
|
1582
|
+
dragLayerRef.current?.hide();
|
|
1583
|
+
active = false;
|
|
1584
|
+
activePointerId = null;
|
|
1585
|
+
onDragEnd();
|
|
1586
|
+
};
|
|
1587
|
+
const onPointerDown = (e) => {
|
|
1588
|
+
if (e.button !== 0) return;
|
|
1589
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
1590
|
+
if (sq === null) return;
|
|
1591
|
+
const cell = game.getSnapshot().board[sq] ?? 0;
|
|
1592
|
+
if (cell === 0) return;
|
|
1593
|
+
const canDrag = canDragPieceRef.current;
|
|
1594
|
+
if (canDrag !== void 0 && !canDrag({ square: sq, cell })) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const consumed = controller.pointerDown(sq, e.pointerId, e.clientX, e.clientY);
|
|
1598
|
+
if (consumed) activePointerId = e.pointerId;
|
|
1599
|
+
};
|
|
1600
|
+
const onPointerMove = (e) => {
|
|
1601
|
+
const evt = controller.pointerMove(e.pointerId, e.clientX, e.clientY);
|
|
1602
|
+
if (evt === null) return;
|
|
1603
|
+
if (evt.kind === "drag-start") {
|
|
1604
|
+
const cell = game.getSnapshot().board[evt.from] ?? 0;
|
|
1605
|
+
if (cell === 0) {
|
|
1606
|
+
controller.cancel();
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
const originLabel = algebraicOf6(evt.from);
|
|
1610
|
+
originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
|
|
1611
|
+
if (originEl !== null) {
|
|
1612
|
+
originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
|
|
1613
|
+
}
|
|
1614
|
+
active = true;
|
|
1615
|
+
try {
|
|
1616
|
+
container.setPointerCapture(e.pointerId);
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1619
|
+
dragLayerRef.current?.show(cell);
|
|
1620
|
+
writeTransform(e.clientX, e.clientY);
|
|
1621
|
+
onDragStart(evt.from, cell);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
writeTransform(e.clientX, e.clientY);
|
|
1625
|
+
};
|
|
1626
|
+
const onPointerUp = (e) => {
|
|
1627
|
+
const evt = controller.pointerUp(e.pointerId, e.clientX, e.clientY);
|
|
1628
|
+
if (evt === null) return;
|
|
1629
|
+
if (evt.kind === "drop") {
|
|
1630
|
+
const target = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
1631
|
+
if (target !== null && target !== evt.from) {
|
|
1632
|
+
onDrop(evt.from, target);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (active) cleanup();
|
|
1636
|
+
activePointerId = null;
|
|
1637
|
+
};
|
|
1638
|
+
const onPointerCancel = (e) => {
|
|
1639
|
+
if (activePointerId !== null && e.pointerId !== activePointerId) return;
|
|
1640
|
+
controller.cancel();
|
|
1641
|
+
if (active) cleanup();
|
|
1642
|
+
activePointerId = null;
|
|
1643
|
+
};
|
|
1644
|
+
container.addEventListener("pointerdown", onPointerDown);
|
|
1645
|
+
container.addEventListener("pointermove", onPointerMove, { passive: true });
|
|
1646
|
+
container.addEventListener("pointerup", onPointerUp);
|
|
1647
|
+
container.addEventListener("pointercancel", onPointerCancel);
|
|
1648
|
+
return () => {
|
|
1649
|
+
container.removeEventListener("pointerdown", onPointerDown);
|
|
1650
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
1651
|
+
container.removeEventListener("pointerup", onPointerUp);
|
|
1652
|
+
container.removeEventListener("pointercancel", onPointerCancel);
|
|
1653
|
+
if (active) cleanup();
|
|
1654
|
+
};
|
|
1655
|
+
}, [
|
|
1656
|
+
game,
|
|
1657
|
+
orientation,
|
|
1658
|
+
containerRef,
|
|
1659
|
+
dragLayerRef,
|
|
1660
|
+
enabled,
|
|
1661
|
+
activationDistance,
|
|
1662
|
+
onDragStart,
|
|
1663
|
+
onDragEnd,
|
|
1664
|
+
onDrop
|
|
1665
|
+
]);
|
|
1666
|
+
}
|
|
1667
|
+
function useHoverSquare(options) {
|
|
1668
|
+
const { game, orientation, containerRef, enabled, onEnter, onLeave } = options;
|
|
1669
|
+
const onEnterRef = react.useRef(onEnter);
|
|
1670
|
+
const onLeaveRef = react.useRef(onLeave);
|
|
1671
|
+
react.useEffect(() => {
|
|
1672
|
+
onEnterRef.current = onEnter;
|
|
1673
|
+
onLeaveRef.current = onLeave;
|
|
1674
|
+
});
|
|
1675
|
+
react.useEffect(() => {
|
|
1676
|
+
if (!enabled) return;
|
|
1677
|
+
const container = containerRef.current;
|
|
1678
|
+
if (container === null) return;
|
|
1679
|
+
let current = null;
|
|
1680
|
+
const cellAt = (sq) => game === null ? 0 : game.getSnapshot().board[sq] ?? 0;
|
|
1681
|
+
const onPointerMove = (e) => {
|
|
1682
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
1683
|
+
if (sq === current) return;
|
|
1684
|
+
if (current !== null) onLeaveRef.current?.({ square: current, cell: cellAt(current) });
|
|
1685
|
+
if (sq !== null) onEnterRef.current?.({ square: sq, cell: cellAt(sq) });
|
|
1686
|
+
current = sq;
|
|
1687
|
+
};
|
|
1688
|
+
const onPointerLeave = () => {
|
|
1689
|
+
if (current === null) return;
|
|
1690
|
+
onLeaveRef.current?.({ square: current, cell: cellAt(current) });
|
|
1691
|
+
current = null;
|
|
1692
|
+
};
|
|
1693
|
+
container.addEventListener("pointermove", onPointerMove, { passive: true });
|
|
1694
|
+
container.addEventListener("pointerleave", onPointerLeave);
|
|
1695
|
+
return () => {
|
|
1696
|
+
container.removeEventListener("pointermove", onPointerMove);
|
|
1697
|
+
container.removeEventListener("pointerleave", onPointerLeave);
|
|
1698
|
+
if (current !== null) {
|
|
1699
|
+
onLeaveRef.current?.({ square: current, cell: cellAt(current) });
|
|
1700
|
+
current = null;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
}, [game, orientation, containerRef, enabled]);
|
|
1704
|
+
}
|
|
1705
|
+
function moveFocus(current, direction, orientation) {
|
|
1706
|
+
const file = current & 7;
|
|
1707
|
+
const rank = current >> 3;
|
|
1708
|
+
const col = orientation === "white" ? file : 7 - file;
|
|
1709
|
+
const row = orientation === "white" ? 7 - rank : rank;
|
|
1710
|
+
let newCol = col;
|
|
1711
|
+
let newRow = row;
|
|
1712
|
+
switch (direction) {
|
|
1713
|
+
case "up":
|
|
1714
|
+
newRow = Math.max(0, row - 1);
|
|
1715
|
+
break;
|
|
1716
|
+
case "down":
|
|
1717
|
+
newRow = Math.min(7, row + 1);
|
|
1718
|
+
break;
|
|
1719
|
+
case "left":
|
|
1720
|
+
newCol = Math.max(0, col - 1);
|
|
1721
|
+
break;
|
|
1722
|
+
case "right":
|
|
1723
|
+
newCol = Math.min(7, col + 1);
|
|
1724
|
+
break;
|
|
1725
|
+
}
|
|
1726
|
+
const newFile = orientation === "white" ? newCol : 7 - newCol;
|
|
1727
|
+
const newRank = orientation === "white" ? 7 - newRow : newRow;
|
|
1728
|
+
return newRank * 8 + newFile;
|
|
1729
|
+
}
|
|
1730
|
+
function rankEnd(current, which, orientation) {
|
|
1731
|
+
const rank = current >> 3;
|
|
1732
|
+
if (orientation === "white") {
|
|
1733
|
+
return rank << 3 | (which === "home" ? 0 : 7);
|
|
1734
|
+
}
|
|
1735
|
+
return rank << 3 | (which === "home" ? 7 : 0);
|
|
1736
|
+
}
|
|
1737
|
+
function boardCorner(which, orientation) {
|
|
1738
|
+
if (orientation === "white") {
|
|
1739
|
+
return which === "home" ? 56 : 7;
|
|
1740
|
+
}
|
|
1741
|
+
return which === "home" ? 7 : 56;
|
|
1742
|
+
}
|
|
1743
|
+
function useKeyboardNav(options) {
|
|
1744
|
+
const {
|
|
1745
|
+
containerRef,
|
|
1746
|
+
orientation,
|
|
1747
|
+
focusedSquare,
|
|
1748
|
+
setFocusedSquare,
|
|
1749
|
+
onActivate,
|
|
1750
|
+
onEscape,
|
|
1751
|
+
enabled
|
|
1752
|
+
} = options;
|
|
1753
|
+
react.useEffect(() => {
|
|
1754
|
+
if (!enabled) return;
|
|
1755
|
+
const container = containerRef.current;
|
|
1756
|
+
if (container === null) return;
|
|
1757
|
+
const onKeyDown = (e) => {
|
|
1758
|
+
if (!container.contains(e.target)) return;
|
|
1759
|
+
const current = focusedSquare;
|
|
1760
|
+
if (current === null) return;
|
|
1761
|
+
switch (e.key) {
|
|
1762
|
+
case "ArrowUp":
|
|
1763
|
+
e.preventDefault();
|
|
1764
|
+
setFocusedSquare(moveFocus(current, "up", orientation));
|
|
1765
|
+
return;
|
|
1766
|
+
case "ArrowDown":
|
|
1767
|
+
e.preventDefault();
|
|
1768
|
+
setFocusedSquare(moveFocus(current, "down", orientation));
|
|
1769
|
+
return;
|
|
1770
|
+
case "ArrowLeft":
|
|
1771
|
+
e.preventDefault();
|
|
1772
|
+
setFocusedSquare(moveFocus(current, "left", orientation));
|
|
1773
|
+
return;
|
|
1774
|
+
case "ArrowRight":
|
|
1775
|
+
e.preventDefault();
|
|
1776
|
+
setFocusedSquare(moveFocus(current, "right", orientation));
|
|
1777
|
+
return;
|
|
1778
|
+
case "Home":
|
|
1779
|
+
e.preventDefault();
|
|
1780
|
+
setFocusedSquare(
|
|
1781
|
+
e.ctrlKey || e.metaKey ? boardCorner("home", orientation) : rankEnd(current, "home", orientation)
|
|
1782
|
+
);
|
|
1783
|
+
return;
|
|
1784
|
+
case "End":
|
|
1785
|
+
e.preventDefault();
|
|
1786
|
+
setFocusedSquare(
|
|
1787
|
+
e.ctrlKey || e.metaKey ? boardCorner("end", orientation) : rankEnd(current, "end", orientation)
|
|
1788
|
+
);
|
|
1789
|
+
return;
|
|
1790
|
+
case "Enter":
|
|
1791
|
+
case " ":
|
|
1792
|
+
e.preventDefault();
|
|
1793
|
+
onActivate(current);
|
|
1794
|
+
return;
|
|
1795
|
+
case "Escape":
|
|
1796
|
+
e.preventDefault();
|
|
1797
|
+
onEscape();
|
|
1798
|
+
return;
|
|
1799
|
+
default:
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
container.addEventListener("keydown", onKeyDown);
|
|
1804
|
+
return () => container.removeEventListener("keydown", onKeyDown);
|
|
1805
|
+
}, [containerRef, orientation, focusedSquare, setFocusedSquare, onActivate, onEscape, enabled]);
|
|
1806
|
+
}
|
|
1807
|
+
var STYLE_ID2 = "gb-last-move-styles";
|
|
1808
|
+
function injectLastMoveStyles() {
|
|
1809
|
+
if (typeof document === "undefined") return;
|
|
1810
|
+
if (document.getElementById(STYLE_ID2) !== null) return;
|
|
1811
|
+
const style = document.createElement("style");
|
|
1812
|
+
style.id = STYLE_ID2;
|
|
1813
|
+
style.textContent = `
|
|
1814
|
+
[data-gb-square]::after {
|
|
1815
|
+
content: "";
|
|
1816
|
+
position: absolute;
|
|
1817
|
+
inset: 0;
|
|
1818
|
+
background: transparent;
|
|
1819
|
+
pointer-events: none;
|
|
1820
|
+
}
|
|
1821
|
+
[data-gb-square][data-gb-last-move]::after {
|
|
1822
|
+
background: var(--gb-last-move);
|
|
1823
|
+
}
|
|
1824
|
+
`;
|
|
1825
|
+
document.head.appendChild(style);
|
|
1826
|
+
}
|
|
1827
|
+
function useLastMoveController(model, squareRefs, enabled) {
|
|
1828
|
+
react.useEffect(() => {
|
|
1829
|
+
injectLastMoveStyles();
|
|
1830
|
+
}, []);
|
|
1831
|
+
react.useEffect(() => {
|
|
1832
|
+
if (model === null) return;
|
|
1833
|
+
let prevFrom = null;
|
|
1834
|
+
let prevTo = null;
|
|
1835
|
+
const write = (i, val) => {
|
|
1836
|
+
const el = squareRefs.current?.[i];
|
|
1837
|
+
if (el === null || el === void 0) return;
|
|
1838
|
+
if (val === null) {
|
|
1839
|
+
delete el.dataset["gbLastMove"];
|
|
1840
|
+
} else {
|
|
1841
|
+
el.dataset["gbLastMove"] = val;
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
const clearAll = () => {
|
|
1845
|
+
if (prevFrom !== null) write(prevFrom, null);
|
|
1846
|
+
if (prevTo !== null) write(prevTo, null);
|
|
1847
|
+
prevFrom = null;
|
|
1848
|
+
prevTo = null;
|
|
1849
|
+
};
|
|
1850
|
+
if (!enabled) {
|
|
1851
|
+
return clearAll;
|
|
1852
|
+
}
|
|
1853
|
+
const update = () => {
|
|
1854
|
+
const snap = model.getSnapshot();
|
|
1855
|
+
const lastMove = snap.lastMove;
|
|
1856
|
+
let from = null;
|
|
1857
|
+
let to = null;
|
|
1858
|
+
if (lastMove !== null) {
|
|
1859
|
+
from = lastMove & 63;
|
|
1860
|
+
to = lastMove >> 6 & 63;
|
|
1861
|
+
}
|
|
1862
|
+
if (from === prevFrom && to === prevTo) return;
|
|
1863
|
+
if (prevFrom !== null && prevFrom !== from && prevFrom !== to) {
|
|
1864
|
+
write(prevFrom, null);
|
|
1865
|
+
}
|
|
1866
|
+
if (prevTo !== null && prevTo !== from && prevTo !== to) {
|
|
1867
|
+
write(prevTo, null);
|
|
1868
|
+
}
|
|
1869
|
+
if (from !== null && from !== to) write(from, "from");
|
|
1870
|
+
if (to !== null) write(to, "to");
|
|
1871
|
+
prevFrom = from;
|
|
1872
|
+
prevTo = to;
|
|
1873
|
+
};
|
|
1874
|
+
update();
|
|
1875
|
+
const unsubscribe = model.subscribe(update);
|
|
1876
|
+
return () => {
|
|
1877
|
+
unsubscribe();
|
|
1878
|
+
clearAll();
|
|
1879
|
+
};
|
|
1880
|
+
}, [model, squareRefs, enabled]);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/lib/haptics.ts
|
|
1884
|
+
var COARSE_POINTER_QUERY = "(hover: none), (pointer: coarse)";
|
|
1885
|
+
var cached;
|
|
1886
|
+
var pending = null;
|
|
1887
|
+
function isCoarsePointer() {
|
|
1888
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
1889
|
+
return false;
|
|
1890
|
+
}
|
|
1891
|
+
return window.matchMedia(COARSE_POINTER_QUERY).matches;
|
|
1892
|
+
}
|
|
1893
|
+
function shouldRunHaptics(options) {
|
|
1894
|
+
if (options.enabled === false) return false;
|
|
1895
|
+
if (options.mobileOnly === false) return true;
|
|
1896
|
+
return isCoarsePointer();
|
|
1897
|
+
}
|
|
1898
|
+
async function getInstance() {
|
|
1899
|
+
if (cached !== void 0) {
|
|
1900
|
+
return cached;
|
|
1901
|
+
}
|
|
1902
|
+
if (pending !== null) {
|
|
1903
|
+
return pending;
|
|
1904
|
+
}
|
|
1905
|
+
if (typeof window === "undefined") {
|
|
1906
|
+
cached = null;
|
|
1907
|
+
return cached;
|
|
1908
|
+
}
|
|
1909
|
+
pending = import('web-haptics').then(({ WebHaptics }) => {
|
|
1910
|
+
try {
|
|
1911
|
+
cached = new WebHaptics();
|
|
1912
|
+
} catch {
|
|
1913
|
+
cached = null;
|
|
1914
|
+
}
|
|
1915
|
+
return cached;
|
|
1916
|
+
}).catch(() => {
|
|
1917
|
+
cached = null;
|
|
1918
|
+
return cached;
|
|
1919
|
+
}).finally(() => {
|
|
1920
|
+
pending = null;
|
|
1921
|
+
});
|
|
1922
|
+
return pending;
|
|
1923
|
+
}
|
|
1924
|
+
function triggerHaptic(input, options = {}) {
|
|
1925
|
+
if (!shouldRunHaptics(options)) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
const { enabled: _enabled, mobileOnly: _mobileOnly, ...triggerOptions } = options;
|
|
1929
|
+
void getInstance().then((instance) => {
|
|
1930
|
+
void instance?.trigger(input, triggerOptions);
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
function warmupHaptics(options = {}) {
|
|
1934
|
+
if (!shouldRunHaptics(options)) {
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
void getInstance();
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
// src/lib/move-feedback.ts
|
|
1941
|
+
var DEFAULT_MOVE_HAPTIC_PATTERNS = Object.freeze({
|
|
1942
|
+
moveSelf: "selection",
|
|
1943
|
+
moveOpponent: "selection",
|
|
1944
|
+
capture: "medium",
|
|
1945
|
+
castle: "medium",
|
|
1946
|
+
moveCheck: "success",
|
|
1947
|
+
promote: "success",
|
|
1948
|
+
gameEnd: "success"
|
|
1949
|
+
});
|
|
1950
|
+
function classifyMoveFeedback(animations, snapshot, perspective) {
|
|
1951
|
+
if (snapshot.isGameOver) return "gameEnd";
|
|
1952
|
+
let sawPromotion = false;
|
|
1953
|
+
let sawCastle = false;
|
|
1954
|
+
let sawCapture = false;
|
|
1955
|
+
for (const d of animations) {
|
|
1956
|
+
if (d.kind === "promotion") sawPromotion = true;
|
|
1957
|
+
else if (d.kind === "castle") sawCastle = true;
|
|
1958
|
+
else if (d.kind === "capture" || d.kind === "en-passant") sawCapture = true;
|
|
1959
|
+
}
|
|
1960
|
+
if (sawPromotion) return "promote";
|
|
1961
|
+
if (sawCastle) return "castle";
|
|
1962
|
+
if (snapshot.inCheck) return "moveCheck";
|
|
1963
|
+
if (sawCapture) return "capture";
|
|
1964
|
+
if (perspective !== void 0) {
|
|
1965
|
+
const moverWasMe = snapshot.turn !== perspective;
|
|
1966
|
+
return moverWasMe ? "moveSelf" : "moveOpponent";
|
|
1967
|
+
}
|
|
1968
|
+
return "moveSelf";
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// src/hooks/use-move-haptics.ts
|
|
1972
|
+
var DEFAULT_MIN_INTERVAL_MS = 14;
|
|
1973
|
+
function triggerMoveHaptic(key, {
|
|
1974
|
+
enabled = true,
|
|
1975
|
+
patterns,
|
|
1976
|
+
mobileOnly = true,
|
|
1977
|
+
intensity
|
|
1978
|
+
} = {}) {
|
|
1979
|
+
if (!enabled) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const input = patterns?.[key] ?? DEFAULT_MOVE_HAPTIC_PATTERNS[key];
|
|
1983
|
+
triggerHaptic(input, {
|
|
1984
|
+
enabled,
|
|
1985
|
+
mobileOnly,
|
|
1986
|
+
...intensity !== void 0 ? { intensity } : {}
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
function useMoveHaptics(model, options = {}) {
|
|
1990
|
+
const {
|
|
1991
|
+
enabled = true,
|
|
1992
|
+
patterns,
|
|
1993
|
+
perspective,
|
|
1994
|
+
mobileOnly = true,
|
|
1995
|
+
minIntervalMs = DEFAULT_MIN_INTERVAL_MS,
|
|
1996
|
+
warmupOnFirstInteraction = true,
|
|
1997
|
+
intensity
|
|
1998
|
+
} = options;
|
|
1999
|
+
const patternsRef = react.useRef(patterns);
|
|
2000
|
+
react.useEffect(() => {
|
|
2001
|
+
patternsRef.current = patterns;
|
|
2002
|
+
});
|
|
2003
|
+
const perspectiveRef = react.useRef(perspective);
|
|
2004
|
+
react.useEffect(() => {
|
|
2005
|
+
perspectiveRef.current = perspective;
|
|
2006
|
+
});
|
|
2007
|
+
const mobileOnlyRef = react.useRef(mobileOnly);
|
|
2008
|
+
react.useEffect(() => {
|
|
2009
|
+
mobileOnlyRef.current = mobileOnly;
|
|
2010
|
+
});
|
|
2011
|
+
const intensityRef = react.useRef(intensity);
|
|
2012
|
+
react.useEffect(() => {
|
|
2013
|
+
intensityRef.current = intensity;
|
|
2014
|
+
});
|
|
2015
|
+
const lastTriggerAtRef = react.useRef(0);
|
|
2016
|
+
const trigger = react.useCallback(
|
|
2017
|
+
(key) => {
|
|
2018
|
+
triggerMoveHaptic(key, {
|
|
2019
|
+
enabled,
|
|
2020
|
+
...patternsRef.current !== void 0 ? { patterns: patternsRef.current } : {},
|
|
2021
|
+
mobileOnly: mobileOnlyRef.current,
|
|
2022
|
+
...intensityRef.current !== void 0 ? { intensity: intensityRef.current } : {}
|
|
2023
|
+
});
|
|
2024
|
+
},
|
|
2025
|
+
[enabled]
|
|
2026
|
+
);
|
|
2027
|
+
react.useEffect(() => {
|
|
2028
|
+
if (!enabled || !warmupOnFirstInteraction) {
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
let warmed = false;
|
|
2032
|
+
const warmup = () => {
|
|
2033
|
+
if (warmed) {
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
warmed = true;
|
|
2037
|
+
warmupHaptics({ mobileOnly: mobileOnlyRef.current });
|
|
2038
|
+
window.removeEventListener("pointerdown", warmup, true);
|
|
2039
|
+
window.removeEventListener("touchstart", warmup, true);
|
|
2040
|
+
window.removeEventListener("keydown", warmup, true);
|
|
2041
|
+
};
|
|
2042
|
+
window.addEventListener("pointerdown", warmup, { capture: true, passive: true });
|
|
2043
|
+
window.addEventListener("touchstart", warmup, { capture: true, passive: true });
|
|
2044
|
+
window.addEventListener("keydown", warmup, { capture: true });
|
|
2045
|
+
return () => {
|
|
2046
|
+
window.removeEventListener("pointerdown", warmup, true);
|
|
2047
|
+
window.removeEventListener("touchstart", warmup, true);
|
|
2048
|
+
window.removeEventListener("keydown", warmup, true);
|
|
2049
|
+
};
|
|
2050
|
+
}, [enabled, warmupOnFirstInteraction]);
|
|
2051
|
+
react.useEffect(() => {
|
|
2052
|
+
if (!enabled || model === null) return;
|
|
2053
|
+
let prevPly = model.getSnapshot().historyPly;
|
|
2054
|
+
return model.subscribe(() => {
|
|
2055
|
+
const snap = model.getSnapshot();
|
|
2056
|
+
if (snap.historyPly <= prevPly) {
|
|
2057
|
+
prevPly = snap.historyPly;
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
prevPly = snap.historyPly;
|
|
2061
|
+
const nowMs = performance.now();
|
|
2062
|
+
if (nowMs - lastTriggerAtRef.current < minIntervalMs) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
lastTriggerAtRef.current = nowMs;
|
|
2066
|
+
const key = classifyMoveFeedback(model.lastAnimations, snap, perspectiveRef.current);
|
|
2067
|
+
trigger(key);
|
|
2068
|
+
});
|
|
2069
|
+
}, [enabled, minIntervalMs, model, trigger]);
|
|
2070
|
+
return trigger;
|
|
2071
|
+
}
|
|
2072
|
+
var DEFAULT_MOVE_SOUND_SOURCES = Object.freeze({
|
|
2073
|
+
moveSelf: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-self.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-self.mp3",
|
|
2074
|
+
moveOpponent: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-opponent.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-opponent.mp3",
|
|
2075
|
+
capture: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/capture.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/capture.mp3",
|
|
2076
|
+
castle: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/castle.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/castle.mp3",
|
|
2077
|
+
moveCheck: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-check.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-check.mp3",
|
|
2078
|
+
promote: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/promote.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/promote.mp3",
|
|
2079
|
+
gameEnd: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/game-end.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/game-end.mp3"
|
|
2080
|
+
});
|
|
2081
|
+
var POOL_SIZE = 4;
|
|
2082
|
+
function createPools(sources, preload) {
|
|
2083
|
+
const pools = {};
|
|
2084
|
+
for (const key of Object.keys(sources)) {
|
|
2085
|
+
const url = sources[key];
|
|
2086
|
+
const elements = [];
|
|
2087
|
+
for (let i = 0; i < POOL_SIZE; i++) {
|
|
2088
|
+
const audio = new Audio(url);
|
|
2089
|
+
audio.preload = preload ?? "none";
|
|
2090
|
+
elements.push(audio);
|
|
2091
|
+
}
|
|
2092
|
+
pools[key] = { elements, cursor: 0 };
|
|
2093
|
+
}
|
|
2094
|
+
return pools;
|
|
2095
|
+
}
|
|
2096
|
+
function useMoveSound(model, options = {}) {
|
|
2097
|
+
const { enabled = true, volume = 1, preload = "none", sources, perspective } = options;
|
|
2098
|
+
const poolsRef = react.useRef(null);
|
|
2099
|
+
const sourcesRef = react.useRef(DEFAULT_MOVE_SOUND_SOURCES);
|
|
2100
|
+
const preloadRef = react.useRef(preload);
|
|
2101
|
+
const volumeRef = react.useRef(volume);
|
|
2102
|
+
react.useEffect(() => {
|
|
2103
|
+
volumeRef.current = volume;
|
|
2104
|
+
});
|
|
2105
|
+
const perspectiveRef = react.useRef(perspective);
|
|
2106
|
+
react.useEffect(() => {
|
|
2107
|
+
perspectiveRef.current = perspective;
|
|
2108
|
+
});
|
|
2109
|
+
react.useEffect(() => {
|
|
2110
|
+
sourcesRef.current = {
|
|
2111
|
+
...DEFAULT_MOVE_SOUND_SOURCES,
|
|
2112
|
+
...sources ?? {}
|
|
2113
|
+
};
|
|
2114
|
+
poolsRef.current = null;
|
|
2115
|
+
}, [sources]);
|
|
2116
|
+
react.useEffect(() => {
|
|
2117
|
+
preloadRef.current = preload;
|
|
2118
|
+
poolsRef.current = null;
|
|
2119
|
+
}, [preload]);
|
|
2120
|
+
react.useEffect(() => {
|
|
2121
|
+
if (!enabled) {
|
|
2122
|
+
poolsRef.current = null;
|
|
2123
|
+
}
|
|
2124
|
+
}, [enabled]);
|
|
2125
|
+
const triggerMoveSound = react.useCallback(
|
|
2126
|
+
(key) => {
|
|
2127
|
+
if (!enabled) return;
|
|
2128
|
+
if (typeof window === "undefined") return;
|
|
2129
|
+
if (typeof Audio === "undefined") return;
|
|
2130
|
+
const pools = poolsRef.current ?? createPools(sourcesRef.current, preloadRef.current);
|
|
2131
|
+
poolsRef.current = pools;
|
|
2132
|
+
const pool = pools[key];
|
|
2133
|
+
if (pool === void 0) return;
|
|
2134
|
+
pool.cursor = (pool.cursor + 1) % pool.elements.length;
|
|
2135
|
+
const el = pool.elements[pool.cursor];
|
|
2136
|
+
if (el === void 0) return;
|
|
2137
|
+
el.volume = Math.max(0, Math.min(1, volumeRef.current));
|
|
2138
|
+
el.currentTime = 0;
|
|
2139
|
+
void el.play().catch(() => {
|
|
2140
|
+
});
|
|
2141
|
+
},
|
|
2142
|
+
[enabled]
|
|
2143
|
+
);
|
|
2144
|
+
react.useEffect(() => {
|
|
2145
|
+
if (!enabled || model === null) return;
|
|
2146
|
+
let prevPly = model.getSnapshot().historyPly;
|
|
2147
|
+
return model.subscribe(() => {
|
|
2148
|
+
const snap = model.getSnapshot();
|
|
2149
|
+
if (snap.historyPly <= prevPly) {
|
|
2150
|
+
prevPly = snap.historyPly;
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
prevPly = snap.historyPly;
|
|
2154
|
+
const key = classifyMoveFeedback(model.lastAnimations, snap, perspectiveRef.current);
|
|
2155
|
+
triggerMoveSound(key);
|
|
2156
|
+
});
|
|
2157
|
+
}, [enabled, model, triggerMoveSound]);
|
|
2158
|
+
return triggerMoveSound;
|
|
2159
|
+
}
|
|
2160
|
+
var STYLE_ID3 = "gb-selection-styles";
|
|
2161
|
+
function injectSelectionStyles() {
|
|
2162
|
+
if (typeof document === "undefined") return;
|
|
2163
|
+
if (document.getElementById(STYLE_ID3) !== null) return;
|
|
2164
|
+
const style = document.createElement("style");
|
|
2165
|
+
style.id = STYLE_ID3;
|
|
2166
|
+
style.textContent = `
|
|
2167
|
+
[data-gb-square]::before {
|
|
2168
|
+
content: "";
|
|
2169
|
+
position: absolute;
|
|
2170
|
+
inset: 0;
|
|
2171
|
+
background: transparent;
|
|
2172
|
+
pointer-events: none;
|
|
2173
|
+
}
|
|
2174
|
+
[data-gb-square][data-gb-selection="selected"]::before {
|
|
2175
|
+
background: var(--gb-selected);
|
|
2176
|
+
}
|
|
2177
|
+
[data-gb-square][data-gb-selection="legal-quiet"]::before {
|
|
2178
|
+
background-image: radial-gradient(var(--gb-legal-target) 22%, transparent 24%);
|
|
2179
|
+
}
|
|
2180
|
+
[data-gb-square][data-gb-selection="legal-capture"]::before {
|
|
2181
|
+
background-image: radial-gradient(transparent 0 75%, var(--gb-legal-target-capture) 77% 83%, transparent 85%);
|
|
2182
|
+
}
|
|
2183
|
+
[data-gb-target-style="dots"] [data-gb-square][data-gb-selection="legal-quiet"]::before {
|
|
2184
|
+
background-image: radial-gradient(var(--gb-legal-target) 14%, transparent 16%);
|
|
2185
|
+
}
|
|
2186
|
+
[data-gb-target-style="dots"] [data-gb-square][data-gb-selection="legal-capture"]::before {
|
|
2187
|
+
background-image: radial-gradient(var(--gb-legal-target-capture) 20%, transparent 22%);
|
|
2188
|
+
}
|
|
2189
|
+
`;
|
|
2190
|
+
document.head.appendChild(style);
|
|
2191
|
+
}
|
|
2192
|
+
function useSelectionController(model, squareRefs, style) {
|
|
2193
|
+
react.useEffect(() => {
|
|
2194
|
+
injectSelectionStyles();
|
|
2195
|
+
}, []);
|
|
2196
|
+
react.useEffect(() => {
|
|
2197
|
+
if (model === null) return;
|
|
2198
|
+
let prevSelected = null;
|
|
2199
|
+
let prevTargets = /* @__PURE__ */ new Set();
|
|
2200
|
+
const write = (i, val) => {
|
|
2201
|
+
const el = squareRefs.current?.[i];
|
|
2202
|
+
if (el === null || el === void 0) return;
|
|
2203
|
+
if (val === "none") {
|
|
2204
|
+
delete el.dataset["gbSelection"];
|
|
2205
|
+
} else {
|
|
2206
|
+
el.dataset["gbSelection"] = val;
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
const clear = () => {
|
|
2210
|
+
if (prevSelected !== null) write(prevSelected, "none");
|
|
2211
|
+
for (const sq of prevTargets) write(sq, "none");
|
|
2212
|
+
prevSelected = null;
|
|
2213
|
+
prevTargets = /* @__PURE__ */ new Set();
|
|
2214
|
+
};
|
|
2215
|
+
const update = () => {
|
|
2216
|
+
const snap = model.getSnapshot();
|
|
2217
|
+
const { selected, legalTargets, board } = snap;
|
|
2218
|
+
if (style === false) {
|
|
2219
|
+
for (const sq of prevTargets) {
|
|
2220
|
+
if (sq !== selected) write(sq, "none");
|
|
2221
|
+
}
|
|
2222
|
+
if (prevSelected !== null && prevSelected !== selected) {
|
|
2223
|
+
write(prevSelected, "none");
|
|
2224
|
+
}
|
|
2225
|
+
if (selected !== null) write(selected, "selected");
|
|
2226
|
+
prevSelected = selected;
|
|
2227
|
+
prevTargets = /* @__PURE__ */ new Set();
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
if (selected === prevSelected && legalTargets === prevTargets) return;
|
|
2231
|
+
if (prevSelected !== null && prevSelected !== selected && !legalTargets.has(prevSelected)) {
|
|
2232
|
+
write(prevSelected, "none");
|
|
2233
|
+
}
|
|
2234
|
+
for (const sq of prevTargets) {
|
|
2235
|
+
if (sq !== selected && !legalTargets.has(sq)) write(sq, "none");
|
|
2236
|
+
}
|
|
2237
|
+
if (selected !== null) write(selected, "selected");
|
|
2238
|
+
for (const sq of legalTargets) {
|
|
2239
|
+
if (sq === selected) continue;
|
|
2240
|
+
const targetCell = board[sq] ?? 0;
|
|
2241
|
+
write(sq, targetCell === 0 ? "legal-quiet" : "legal-capture");
|
|
2242
|
+
}
|
|
2243
|
+
prevSelected = selected;
|
|
2244
|
+
prevTargets = legalTargets;
|
|
2245
|
+
};
|
|
2246
|
+
update();
|
|
2247
|
+
const unsubscribe = model.subscribe(update);
|
|
2248
|
+
return () => {
|
|
2249
|
+
unsubscribe();
|
|
2250
|
+
clear();
|
|
2251
|
+
};
|
|
2252
|
+
}, [model, squareRefs, style]);
|
|
2253
|
+
}
|
|
2254
|
+
function colorOfCell(cell) {
|
|
2255
|
+
return cell > 6 ? 1 : 0;
|
|
2256
|
+
}
|
|
2257
|
+
function movePromotesPawn(fromCell, to) {
|
|
2258
|
+
if (fromCell === 0) return false;
|
|
2259
|
+
const pieceType = (fromCell - 1) % 6;
|
|
2260
|
+
if (pieceType !== 0) return false;
|
|
2261
|
+
const toRank = to >> 3;
|
|
2262
|
+
return toRank === 0 || toRank === 7;
|
|
2263
|
+
}
|
|
2264
|
+
var ILLEGAL_FLASH_HOLD_MS = 320;
|
|
2265
|
+
var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
|
|
2266
|
+
var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
|
|
2267
|
+
var UCI_TRANSITION_RE = /^([a-h][1-8])([a-h][1-8])([nbrq])?$/i;
|
|
2268
|
+
function prefersReducedMotion3() {
|
|
2269
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2270
|
+
return false;
|
|
2271
|
+
}
|
|
2272
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2273
|
+
}
|
|
2274
|
+
function cancelElementAnimations(element) {
|
|
2275
|
+
if (typeof element.getAnimations !== "function") return;
|
|
2276
|
+
for (const anim of element.getAnimations()) anim.cancel();
|
|
2277
|
+
}
|
|
2278
|
+
function queryPieceAt(container, square) {
|
|
2279
|
+
return container.querySelector(`[data-piece-square="${square}"]`);
|
|
2280
|
+
}
|
|
2281
|
+
function squareIndexOfName(square) {
|
|
2282
|
+
const file = square.charCodeAt(0) - 97;
|
|
2283
|
+
const rank = Number(square[1]) - 1;
|
|
2284
|
+
return rank << 3 | file;
|
|
2285
|
+
}
|
|
2286
|
+
function deltaFromSquareNames(from, to, squareSize, orientation) {
|
|
2287
|
+
const fromIndex = squareIndexOfName(from);
|
|
2288
|
+
const toIndex = squareIndexOfName(to);
|
|
2289
|
+
const fromFile = fromIndex & 7;
|
|
2290
|
+
const fromRank = fromIndex >> 3;
|
|
2291
|
+
const toFile = toIndex & 7;
|
|
2292
|
+
const toRank = toIndex >> 3;
|
|
2293
|
+
const fromCol = orientation === "white" ? fromFile : 7 - fromFile;
|
|
2294
|
+
const fromRow = orientation === "white" ? 7 - fromRank : fromRank;
|
|
2295
|
+
const toCol = orientation === "white" ? toFile : 7 - toFile;
|
|
2296
|
+
const toRow = orientation === "white" ? 7 - toRank : toRank;
|
|
2297
|
+
return {
|
|
2298
|
+
dx: (fromCol - toCol) * squareSize,
|
|
2299
|
+
dy: (fromRow - toRow) * squareSize
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
function castleRookTransition(from, to) {
|
|
2303
|
+
const fromFile = from.charCodeAt(0);
|
|
2304
|
+
const toFile = to.charCodeAt(0);
|
|
2305
|
+
if (fromFile !== 101 || Math.abs(toFile - fromFile) !== 2) return null;
|
|
2306
|
+
const rank = from[1];
|
|
2307
|
+
if (rank !== "1" && rank !== "8") return null;
|
|
2308
|
+
const kingSide = toFile > fromFile;
|
|
2309
|
+
return {
|
|
2310
|
+
from: `${kingSide ? "h" : "a"}${rank}`,
|
|
2311
|
+
to: `${kingSide ? "f" : "d"}${rank}`
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
function transitionSquares(transition) {
|
|
2315
|
+
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2316
|
+
if (parsed === null) return [];
|
|
2317
|
+
const moveFrom = parsed[1] ?? "";
|
|
2318
|
+
const moveTo = parsed[2] ?? "";
|
|
2319
|
+
const forward = [{ from: moveFrom, to: moveTo }];
|
|
2320
|
+
const rook = castleRookTransition(moveFrom, moveTo);
|
|
2321
|
+
if (rook !== null) forward.push(rook);
|
|
2322
|
+
return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
|
|
2323
|
+
}
|
|
2324
|
+
function parseTransitionMove(transition) {
|
|
2325
|
+
const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
|
|
2326
|
+
if (parsed === null) return null;
|
|
2327
|
+
const from = parsed[1];
|
|
2328
|
+
const to = parsed[2];
|
|
2329
|
+
if (from === void 0 || to === void 0) return null;
|
|
2330
|
+
return {
|
|
2331
|
+
from: squareIndexOfName(from),
|
|
2332
|
+
to: squareIndexOfName(to),
|
|
2333
|
+
promotion: parsed[3] ?? null
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
function isPawn(cell) {
|
|
2337
|
+
return !chunkUUTEFNFO_cjs.isEmptyCell(cell) && chunkUUTEFNFO_cjs.pieceTypeOf(cell) === 0;
|
|
2338
|
+
}
|
|
2339
|
+
function controlledFeedbackDescriptors(transition, previousBoard, nextBoard) {
|
|
2340
|
+
if (transition === null || transition === void 0) return null;
|
|
2341
|
+
const parsed = parseTransitionMove(transition);
|
|
2342
|
+
if (parsed === null) return null;
|
|
2343
|
+
const before = transition.direction === "forward" ? previousBoard : nextBoard;
|
|
2344
|
+
const after = transition.direction === "forward" ? nextBoard : previousBoard;
|
|
2345
|
+
const movingPiece = before[parsed.from] ?? 0;
|
|
2346
|
+
if (chunkUUTEFNFO_cjs.isEmptyCell(movingPiece)) return null;
|
|
2347
|
+
const fromFile = parsed.from & 7;
|
|
2348
|
+
const toFile = parsed.to & 7;
|
|
2349
|
+
const targetBefore = before[parsed.to] ?? 0;
|
|
2350
|
+
const targetAfter = after[parsed.to] ?? 0;
|
|
2351
|
+
if (chunkUUTEFNFO_cjs.pieceTypeOf(movingPiece) === 5 && Math.abs(toFile - fromFile) === 2) {
|
|
2352
|
+
return [{ kind: "castle" }];
|
|
2353
|
+
}
|
|
2354
|
+
if (parsed.promotion !== null || isPawn(movingPiece) && (parsed.to >> 3 === 0 || parsed.to >> 3 === 7) && !chunkUUTEFNFO_cjs.isEmptyCell(targetAfter) && chunkUUTEFNFO_cjs.pieceTypeOf(targetAfter) !== 0) {
|
|
2355
|
+
return [{ kind: "promotion" }];
|
|
2356
|
+
}
|
|
2357
|
+
if (!chunkUUTEFNFO_cjs.isEmptyCell(targetBefore) && chunkUUTEFNFO_cjs.colorOf(targetBefore) !== chunkUUTEFNFO_cjs.colorOf(movingPiece)) {
|
|
2358
|
+
return [{ kind: "capture" }];
|
|
2359
|
+
}
|
|
2360
|
+
if (isPawn(movingPiece) && fromFile !== toFile && chunkUUTEFNFO_cjs.isEmptyCell(targetBefore)) {
|
|
2361
|
+
const capturedSquare = parsed.from & -8 | toFile;
|
|
2362
|
+
const capturedBefore = before[capturedSquare] ?? 0;
|
|
2363
|
+
const capturedAfter = after[capturedSquare] ?? 0;
|
|
2364
|
+
if (!chunkUUTEFNFO_cjs.isEmptyCell(capturedBefore) && chunkUUTEFNFO_cjs.colorOf(capturedBefore) !== chunkUUTEFNFO_cjs.colorOf(movingPiece) && chunkUUTEFNFO_cjs.isEmptyCell(capturedAfter)) {
|
|
2365
|
+
return [{ kind: "en-passant" }];
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
return [{ kind: "move" }];
|
|
2369
|
+
}
|
|
2370
|
+
function schedulePositionAnimation(container, transitions, options, orientation) {
|
|
2371
|
+
const animate = () => {
|
|
2372
|
+
const rect = container.getBoundingClientRect();
|
|
2373
|
+
const squareSize = rect.width / 8;
|
|
2374
|
+
if (squareSize <= 0) return;
|
|
2375
|
+
for (const item of transitions) {
|
|
2376
|
+
const piece = queryPieceAt(container, item.to);
|
|
2377
|
+
if (piece === null || typeof piece.animate !== "function") continue;
|
|
2378
|
+
const { dx, dy } = deltaFromSquareNames(item.from, item.to, squareSize, orientation);
|
|
2379
|
+
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;
|
|
2380
|
+
cancelElementAnimations(piece);
|
|
2381
|
+
piece.animate(
|
|
2382
|
+
[{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "translate(0, 0)" }],
|
|
2383
|
+
{ duration: options.durationMs, easing: options.easing, fill: "none" }
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
if (typeof window.requestAnimationFrame === "function") {
|
|
2388
|
+
window.requestAnimationFrame(animate);
|
|
2389
|
+
} else {
|
|
2390
|
+
window.setTimeout(animate, 0);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function syncPositionFen(game, targetFen, transition, managedArrows, preserveUserArrows, container, animation, orientation) {
|
|
2394
|
+
const positionChanged = game.engine.fen() !== targetFen;
|
|
2395
|
+
const durationMs = animation?.durationMs ?? DEFAULT_POSITION_SYNC_DURATION_MS;
|
|
2396
|
+
const easing = animation?.easing ?? DEFAULT_POSITION_SYNC_EASING;
|
|
2397
|
+
const shouldAnimate = positionChanged && transition !== null && transition !== void 0 && container !== null && durationMs > 0 && !prefersReducedMotion3();
|
|
2398
|
+
const transitions = [];
|
|
2399
|
+
if (shouldAnimate) {
|
|
2400
|
+
for (const item of transitionSquares(transition)) {
|
|
2401
|
+
if (queryPieceAt(container, item.from) !== null) transitions.push(item);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
try {
|
|
2405
|
+
if (managedArrows === void 0) {
|
|
2406
|
+
if (!positionChanged) return false;
|
|
2407
|
+
game.load(targetFen);
|
|
2408
|
+
} else {
|
|
2409
|
+
game.syncPosition({
|
|
2410
|
+
fen: targetFen,
|
|
2411
|
+
managedArrows,
|
|
2412
|
+
preserveUserArrows: preserveUserArrows === true
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
} catch {
|
|
2416
|
+
return false;
|
|
2417
|
+
}
|
|
2418
|
+
if (transitions.length > 0 && container !== null) {
|
|
2419
|
+
schedulePositionAnimation(container, transitions, { durationMs, easing }, orientation);
|
|
2420
|
+
}
|
|
2421
|
+
return positionChanged;
|
|
2422
|
+
}
|
|
2423
|
+
function Chessboard(props) {
|
|
2424
|
+
const {
|
|
2425
|
+
game,
|
|
2426
|
+
fallbackFen,
|
|
2427
|
+
positionFen,
|
|
2428
|
+
positionTransition = null,
|
|
2429
|
+
managedArrows,
|
|
2430
|
+
preserveUserArrowsOnPositionSync,
|
|
2431
|
+
orientation = "white",
|
|
2432
|
+
theme = chunkI4BQGFP5_cjs.defaultTheme,
|
|
2433
|
+
pieces = chunkI4BQGFP5_cjs.defaultPieces,
|
|
2434
|
+
showCoordinates = true,
|
|
2435
|
+
ranksPosition = "left",
|
|
2436
|
+
showLegalTargets = "rings",
|
|
2437
|
+
highlightLastMove = true,
|
|
2438
|
+
onMove,
|
|
2439
|
+
onPromote,
|
|
2440
|
+
renderSquare,
|
|
2441
|
+
style,
|
|
2442
|
+
className,
|
|
2443
|
+
ariaLabel,
|
|
2444
|
+
animation,
|
|
2445
|
+
allowDrag = true,
|
|
2446
|
+
allowDrawingArrows = true,
|
|
2447
|
+
clearArrowsOnMove = true,
|
|
2448
|
+
clearArrowsOnClick = true,
|
|
2449
|
+
arrowColors,
|
|
2450
|
+
snapArrowsToValidMove = false,
|
|
2451
|
+
allowPremove = false,
|
|
2452
|
+
showCheckHighlight = true,
|
|
2453
|
+
showIllegalFlash = true,
|
|
2454
|
+
sound = true,
|
|
2455
|
+
haptics = false,
|
|
2456
|
+
viewOnly = false,
|
|
2457
|
+
canDragPiece,
|
|
2458
|
+
onSquareMouseEnter,
|
|
2459
|
+
onSquareMouseLeave,
|
|
2460
|
+
disableContextMenu = true,
|
|
2461
|
+
getSquareAriaLabel,
|
|
2462
|
+
onSquareFocus,
|
|
2463
|
+
getPromotionPieceAriaLabel
|
|
2464
|
+
} = props;
|
|
2465
|
+
const dragEnabled = allowDrag && !viewOnly;
|
|
2466
|
+
const arrowGestureEnabled = allowDrawingArrows && !viewOnly;
|
|
2467
|
+
const clickToMoveEnabled = !viewOnly;
|
|
2468
|
+
const keyboardNavEnabled = !viewOnly;
|
|
2469
|
+
const fallbackBoard = react.useMemo(() => {
|
|
2470
|
+
if (fallbackFen === void 0 || fallbackFen === "") return null;
|
|
2471
|
+
try {
|
|
2472
|
+
return chunkI4BQGFP5_cjs.parseFenPlacement(fallbackFen);
|
|
2473
|
+
} catch {
|
|
2474
|
+
return null;
|
|
2475
|
+
}
|
|
2476
|
+
}, [fallbackFen]);
|
|
2477
|
+
const soundOptions = react.useMemo(() => {
|
|
2478
|
+
if (sound === true) return { enabled: true };
|
|
2479
|
+
if (sound === false || sound === void 0) return { enabled: false };
|
|
2480
|
+
return sound;
|
|
2481
|
+
}, [sound]);
|
|
2482
|
+
const hapticOptions = react.useMemo(() => {
|
|
2483
|
+
if (haptics === true) return { enabled: true };
|
|
2484
|
+
if (haptics === false || haptics === void 0) return { enabled: false };
|
|
2485
|
+
return haptics;
|
|
2486
|
+
}, [haptics]);
|
|
2487
|
+
const containerRef = react.useRef(null);
|
|
2488
|
+
const dragLayerRef = react.useRef(null);
|
|
2489
|
+
const arrowsLayerRef = react.useRef(null);
|
|
2490
|
+
const lastControlledFeedbackFenRef = react.useRef(null);
|
|
2491
|
+
const squareRefs = react.useRef(new Array(64).fill(null));
|
|
2492
|
+
const setSquareRef = react.useCallback((index, el) => {
|
|
2493
|
+
squareRefs.current[index] = el;
|
|
2494
|
+
}, []);
|
|
2495
|
+
const skipNextAnimationRef = react.useRef(false);
|
|
2496
|
+
const onMoveRef = react.useRef(onMove);
|
|
2497
|
+
const onPromoteRef = react.useRef(onPromote);
|
|
2498
|
+
react.useEffect(() => {
|
|
2499
|
+
onMoveRef.current = onMove;
|
|
2500
|
+
onPromoteRef.current = onPromote;
|
|
2501
|
+
});
|
|
2502
|
+
const triggerMoveSound = useMoveSound(game, soundOptions);
|
|
2503
|
+
const triggerMoveHaptic2 = useMoveHaptics(game, hapticOptions);
|
|
2504
|
+
react.useLayoutEffect(() => {
|
|
2505
|
+
if (game === null || positionFen === void 0 || positionFen === "") return;
|
|
2506
|
+
const previousFeedbackFen = lastControlledFeedbackFenRef.current;
|
|
2507
|
+
const previousSnapshot = game.getSnapshot();
|
|
2508
|
+
if (game.engine.fen() === positionFen && managedArrows === void 0) {
|
|
2509
|
+
lastControlledFeedbackFenRef.current = positionFen;
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
const positionChanged = syncPositionFen(
|
|
2513
|
+
game,
|
|
2514
|
+
positionFen,
|
|
2515
|
+
positionTransition,
|
|
2516
|
+
managedArrows,
|
|
2517
|
+
preserveUserArrowsOnPositionSync,
|
|
2518
|
+
containerRef.current,
|
|
2519
|
+
animation,
|
|
2520
|
+
orientation
|
|
2521
|
+
);
|
|
2522
|
+
lastControlledFeedbackFenRef.current = positionFen;
|
|
2523
|
+
if (!positionChanged || previousFeedbackFen === null || previousFeedbackFen === positionFen) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
const snapshot = game.getSnapshot();
|
|
2527
|
+
const feedbackDescriptors = controlledFeedbackDescriptors(positionTransition, previousSnapshot.board, snapshot.board) ?? game.lastAnimations;
|
|
2528
|
+
const feedbackSnapshot = positionTransition?.direction === "backward" ? previousSnapshot : snapshot;
|
|
2529
|
+
triggerMoveSound(
|
|
2530
|
+
classifyMoveFeedback(feedbackDescriptors, feedbackSnapshot, soundOptions.perspective)
|
|
2531
|
+
);
|
|
2532
|
+
triggerMoveHaptic2(
|
|
2533
|
+
classifyMoveFeedback(feedbackDescriptors, feedbackSnapshot, hapticOptions.perspective)
|
|
2534
|
+
);
|
|
2535
|
+
}, [
|
|
2536
|
+
animation,
|
|
2537
|
+
game,
|
|
2538
|
+
hapticOptions.perspective,
|
|
2539
|
+
positionFen,
|
|
2540
|
+
positionTransition,
|
|
2541
|
+
managedArrows,
|
|
2542
|
+
preserveUserArrowsOnPositionSync,
|
|
2543
|
+
orientation,
|
|
2544
|
+
soundOptions.perspective,
|
|
2545
|
+
triggerMoveHaptic2,
|
|
2546
|
+
triggerMoveSound
|
|
2547
|
+
]);
|
|
2548
|
+
const palette = react.useMemo(() => {
|
|
2549
|
+
const merged = {
|
|
2550
|
+
default: chunkI4BQGFP5_cjs.defaultArrowColors.default,
|
|
2551
|
+
shift: chunkI4BQGFP5_cjs.defaultArrowColors.shift,
|
|
2552
|
+
alt: chunkI4BQGFP5_cjs.defaultArrowColors.alt,
|
|
2553
|
+
ctrl: chunkI4BQGFP5_cjs.defaultArrowColors.ctrl
|
|
2554
|
+
};
|
|
2555
|
+
if (arrowColors !== void 0) {
|
|
2556
|
+
for (const k in arrowColors) {
|
|
2557
|
+
const v = arrowColors[k];
|
|
2558
|
+
if (v !== void 0) merged[k] = v;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
return merged;
|
|
2562
|
+
}, [arrowColors]);
|
|
2563
|
+
const [pendingPromotion, setPendingPromotion] = react.useState(null);
|
|
2564
|
+
const [focusedSquare, setFocusedSquare] = react.useState(null);
|
|
2565
|
+
const defaultKeyboardFocus = react.useCallback(
|
|
2566
|
+
() => orientation === "white" ? 12 : 52,
|
|
2567
|
+
[orientation]
|
|
2568
|
+
);
|
|
2569
|
+
const [flashSquare, setFlashSquare] = react.useState(null);
|
|
2570
|
+
const flashTimerRef = react.useRef(null);
|
|
2571
|
+
const triggerIllegalFlash = react.useCallback(
|
|
2572
|
+
(square) => {
|
|
2573
|
+
if (!showIllegalFlash) return;
|
|
2574
|
+
setFlashSquare(square);
|
|
2575
|
+
if (flashTimerRef.current !== null) clearTimeout(flashTimerRef.current);
|
|
2576
|
+
flashTimerRef.current = setTimeout(() => {
|
|
2577
|
+
setFlashSquare(null);
|
|
2578
|
+
flashTimerRef.current = null;
|
|
2579
|
+
}, ILLEGAL_FLASH_HOLD_MS);
|
|
2580
|
+
},
|
|
2581
|
+
[showIllegalFlash]
|
|
2582
|
+
);
|
|
2583
|
+
react.useEffect(
|
|
2584
|
+
() => () => {
|
|
2585
|
+
if (flashTimerRef.current !== null) clearTimeout(flashTimerRef.current);
|
|
2586
|
+
},
|
|
2587
|
+
[]
|
|
2588
|
+
);
|
|
2589
|
+
react.useEffect(() => {
|
|
2590
|
+
if (game === null || !clearArrowsOnMove) return;
|
|
2591
|
+
let prevPly = game.getSnapshot().historyPly;
|
|
2592
|
+
return game.subscribe(() => {
|
|
2593
|
+
const snap = game.getSnapshot();
|
|
2594
|
+
if (snap.historyPly > prevPly && snap.arrows.length > 0) {
|
|
2595
|
+
game.clearUserArrows();
|
|
2596
|
+
}
|
|
2597
|
+
prevPly = snap.historyPly;
|
|
2598
|
+
});
|
|
2599
|
+
}, [game, clearArrowsOnMove]);
|
|
2600
|
+
const requestPromotion = react.useCallback(
|
|
2601
|
+
(from, to) => {
|
|
2602
|
+
if (game === null) return;
|
|
2603
|
+
const fromCell = game.getSnapshot().board[from] ?? 0;
|
|
2604
|
+
const color = colorOfCell(fromCell);
|
|
2605
|
+
const completeMove = (piece) => {
|
|
2606
|
+
const played = game.tryMove(from, to, piece);
|
|
2607
|
+
const cb = onMoveRef.current;
|
|
2608
|
+
if (played !== null && cb !== void 0) cb(played);
|
|
2609
|
+
};
|
|
2610
|
+
const promoteHandler = onPromoteRef.current;
|
|
2611
|
+
if (promoteHandler !== void 0) {
|
|
2612
|
+
Promise.resolve(promoteHandler({ from, to, color })).then(completeMove, () => {
|
|
2613
|
+
skipNextAnimationRef.current = false;
|
|
2614
|
+
});
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
setPendingPromotion({
|
|
2618
|
+
from,
|
|
2619
|
+
to,
|
|
2620
|
+
color,
|
|
2621
|
+
resolve: (piece) => {
|
|
2622
|
+
setPendingPromotion(null);
|
|
2623
|
+
completeMove(piece);
|
|
2624
|
+
},
|
|
2625
|
+
cancel: () => {
|
|
2626
|
+
skipNextAnimationRef.current = false;
|
|
2627
|
+
setPendingPromotion(null);
|
|
2628
|
+
}
|
|
2629
|
+
});
|
|
2630
|
+
},
|
|
2631
|
+
[game]
|
|
2632
|
+
);
|
|
2633
|
+
const clickToMove = useClickToMove(game, onMove, requestPromotion, clickToMoveEnabled);
|
|
2634
|
+
const maybeClearArrows = react.useCallback(() => {
|
|
2635
|
+
if (!clearArrowsOnClick || game === null) return;
|
|
2636
|
+
if (game.getSnapshot().arrows.length > 0) game.clearUserArrows();
|
|
2637
|
+
}, [clearArrowsOnClick, game]);
|
|
2638
|
+
const handleSquareClick = react.useCallback(
|
|
2639
|
+
(index) => {
|
|
2640
|
+
if (viewOnly) return;
|
|
2641
|
+
maybeClearArrows();
|
|
2642
|
+
clickToMove(index);
|
|
2643
|
+
},
|
|
2644
|
+
[viewOnly, maybeClearArrows, clickToMove]
|
|
2645
|
+
);
|
|
2646
|
+
react.useEffect(() => {
|
|
2647
|
+
if (!clearArrowsOnClick || game === null || viewOnly) return;
|
|
2648
|
+
const onKey = (e) => {
|
|
2649
|
+
if (e.key === "Escape") maybeClearArrows();
|
|
2650
|
+
};
|
|
2651
|
+
window.addEventListener("keydown", onKey);
|
|
2652
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2653
|
+
}, [clearArrowsOnClick, game, maybeClearArrows, viewOnly]);
|
|
2654
|
+
react.useEffect(() => {
|
|
2655
|
+
if (viewOnly || clickToMoveEnabled === false || game === null) return;
|
|
2656
|
+
const container = containerRef.current;
|
|
2657
|
+
if (container === null) return;
|
|
2658
|
+
const onContainerClick = (e) => {
|
|
2659
|
+
if (e.button !== 0) return;
|
|
2660
|
+
const target = e.target;
|
|
2661
|
+
if (target === null) return;
|
|
2662
|
+
if (target.closest("[data-piece-square]") === null) return;
|
|
2663
|
+
const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
|
|
2664
|
+
if (sq === null) return;
|
|
2665
|
+
handleSquareClick(sq);
|
|
2666
|
+
};
|
|
2667
|
+
container.addEventListener("click", onContainerClick);
|
|
2668
|
+
return () => container.removeEventListener("click", onContainerClick);
|
|
2669
|
+
}, [viewOnly, clickToMoveEnabled, game, orientation, handleSquareClick]);
|
|
2670
|
+
react.useEffect(() => {
|
|
2671
|
+
if (!disableContextMenu) return;
|
|
2672
|
+
const container = containerRef.current;
|
|
2673
|
+
if (container === null) return;
|
|
2674
|
+
const onContextMenu = (e) => {
|
|
2675
|
+
e.preventDefault();
|
|
2676
|
+
};
|
|
2677
|
+
container.addEventListener("contextmenu", onContextMenu);
|
|
2678
|
+
return () => container.removeEventListener("contextmenu", onContextMenu);
|
|
2679
|
+
}, [disableContextMenu]);
|
|
2680
|
+
const onDragStart = react.useCallback(
|
|
2681
|
+
(from, _cell) => {
|
|
2682
|
+
maybeClearArrows();
|
|
2683
|
+
const container = containerRef.current;
|
|
2684
|
+
if (container !== null) container.dataset["gbDragging"] = "true";
|
|
2685
|
+
game?.selectSquare(from);
|
|
2686
|
+
},
|
|
2687
|
+
[game, maybeClearArrows]
|
|
2688
|
+
);
|
|
2689
|
+
const onDragEnd = react.useCallback(() => {
|
|
2690
|
+
const container = containerRef.current;
|
|
2691
|
+
if (container !== null) delete container.dataset["gbDragging"];
|
|
2692
|
+
}, []);
|
|
2693
|
+
const onDrop = react.useCallback(
|
|
2694
|
+
(from, to) => {
|
|
2695
|
+
if (game === null) return;
|
|
2696
|
+
const snap = game.getSnapshot();
|
|
2697
|
+
const fromCell = snap.board[from] ?? 0;
|
|
2698
|
+
const pieceColour = colorOfCell(fromCell);
|
|
2699
|
+
if (fromCell !== 0 && pieceColour !== snap.turn) {
|
|
2700
|
+
if (!allowPremove) {
|
|
2701
|
+
triggerIllegalFlash(from);
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
const promotion = movePromotesPawn(fromCell, to) ? 4 : void 0;
|
|
2705
|
+
game.queuePremove(promotion === void 0 ? { from, to } : { from, to, promotion });
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
skipNextAnimationRef.current = true;
|
|
2709
|
+
if (movePromotesPawn(fromCell, to) && game.isLegal(
|
|
2710
|
+
from,
|
|
2711
|
+
to,
|
|
2712
|
+
4
|
|
2713
|
+
/* Queen */
|
|
2714
|
+
)) {
|
|
2715
|
+
requestPromotion(from, to);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
const played = game.tryMove(from, to);
|
|
2719
|
+
const cb = onMoveRef.current;
|
|
2720
|
+
if (played !== null) {
|
|
2721
|
+
if (cb !== void 0) cb(played);
|
|
2722
|
+
} else {
|
|
2723
|
+
skipNextAnimationRef.current = false;
|
|
2724
|
+
triggerIllegalFlash(from);
|
|
2725
|
+
game.selectSquare(from);
|
|
2726
|
+
}
|
|
2727
|
+
},
|
|
2728
|
+
[game, requestPromotion, allowPremove, triggerIllegalFlash]
|
|
2729
|
+
);
|
|
2730
|
+
useDrag({
|
|
2731
|
+
game,
|
|
2732
|
+
orientation,
|
|
2733
|
+
containerRef,
|
|
2734
|
+
dragLayerRef,
|
|
2735
|
+
enabled: dragEnabled && game !== null,
|
|
2736
|
+
onDragStart,
|
|
2737
|
+
onDragEnd,
|
|
2738
|
+
onDrop,
|
|
2739
|
+
...canDragPiece !== void 0 ? { canDragPiece } : {}
|
|
2740
|
+
});
|
|
2741
|
+
react.useEffect(() => {
|
|
2742
|
+
if (!keyboardNavEnabled) {
|
|
2743
|
+
setFocusedSquare(null);
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
setFocusedSquare((current) => current ?? defaultKeyboardFocus());
|
|
2747
|
+
}, [defaultKeyboardFocus, keyboardNavEnabled]);
|
|
2748
|
+
const onKeyboardActivate = react.useCallback(
|
|
2749
|
+
(index) => {
|
|
2750
|
+
maybeClearArrows();
|
|
2751
|
+
clickToMove(index);
|
|
2752
|
+
},
|
|
2753
|
+
[maybeClearArrows, clickToMove]
|
|
2754
|
+
);
|
|
2755
|
+
const onKeyboardEscape = react.useCallback(() => {
|
|
2756
|
+
maybeClearArrows();
|
|
2757
|
+
game?.selectSquare(null);
|
|
2758
|
+
}, [maybeClearArrows, game]);
|
|
2759
|
+
useKeyboardNav({
|
|
2760
|
+
containerRef,
|
|
2761
|
+
orientation,
|
|
2762
|
+
focusedSquare,
|
|
2763
|
+
setFocusedSquare,
|
|
2764
|
+
onActivate: onKeyboardActivate,
|
|
2765
|
+
onEscape: onKeyboardEscape,
|
|
2766
|
+
enabled: game !== null && keyboardNavEnabled
|
|
2767
|
+
});
|
|
2768
|
+
useHoverSquare({
|
|
2769
|
+
game,
|
|
2770
|
+
orientation,
|
|
2771
|
+
containerRef,
|
|
2772
|
+
enabled: onSquareMouseEnter !== void 0 || onSquareMouseLeave !== void 0,
|
|
2773
|
+
...onSquareMouseEnter !== void 0 ? { onEnter: onSquareMouseEnter } : {},
|
|
2774
|
+
...onSquareMouseLeave !== void 0 ? { onLeave: onSquareMouseLeave } : {}
|
|
2775
|
+
});
|
|
2776
|
+
useArrowGesture({
|
|
2777
|
+
game,
|
|
2778
|
+
orientation,
|
|
2779
|
+
containerRef,
|
|
2780
|
+
arrowsLayerRef,
|
|
2781
|
+
enabled: arrowGestureEnabled && game !== null,
|
|
2782
|
+
palette,
|
|
2783
|
+
snapToValidMove: snapArrowsToValidMove
|
|
2784
|
+
});
|
|
2785
|
+
useSelectionController(game, squareRefs, showLegalTargets);
|
|
2786
|
+
useLastMoveController(game, squareRefs, highlightLastMove);
|
|
2787
|
+
useCursorController(game, containerRef, dragEnabled, allowPremove);
|
|
2788
|
+
const animationRuntime = react.useMemo(() => ({ skipNextRef: skipNextAnimationRef }), []);
|
|
2789
|
+
const containerStyle = {
|
|
2790
|
+
position: "relative",
|
|
2791
|
+
aspectRatio: "1 / 1",
|
|
2792
|
+
width: "100%",
|
|
2793
|
+
touchAction: "none",
|
|
2794
|
+
// let the drag layer own pan gestures
|
|
2795
|
+
...theme,
|
|
2796
|
+
...style
|
|
2797
|
+
};
|
|
2798
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2799
|
+
"div",
|
|
2800
|
+
{
|
|
2801
|
+
ref: containerRef,
|
|
2802
|
+
className,
|
|
2803
|
+
style: containerStyle,
|
|
2804
|
+
"data-gb-orientation": orientation,
|
|
2805
|
+
"data-gb-target-style": showLegalTargets === false ? "off" : showLegalTargets,
|
|
2806
|
+
...viewOnly ? { "data-gb-view-only": "true" } : {},
|
|
2807
|
+
children: [
|
|
2808
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2809
|
+
BoardGrid,
|
|
2810
|
+
{
|
|
2811
|
+
orientation,
|
|
2812
|
+
onSquareClick: handleSquareClick,
|
|
2813
|
+
focusedSquare,
|
|
2814
|
+
setSquareRef,
|
|
2815
|
+
readOnly: viewOnly,
|
|
2816
|
+
model: game,
|
|
2817
|
+
getSquareAriaLabel,
|
|
2818
|
+
onSquareFocus,
|
|
2819
|
+
...renderSquare !== void 0 ? { renderSquare } : {},
|
|
2820
|
+
...ariaLabel !== void 0 ? { ariaLabel } : {}
|
|
2821
|
+
}
|
|
2822
|
+
),
|
|
2823
|
+
game !== null && showCheckHighlight ? /* @__PURE__ */ jsxRuntime.jsx(CheckLayer, { model: game, orientation }) : null,
|
|
2824
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(PremoveLayer, { model: game, orientation, pieces }) : null,
|
|
2825
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(ArrowsLayer, { model: game, orientation, palette, below: true }) : null,
|
|
2826
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(PieceLayer, { model: game, orientation, pieces }) : fallbackBoard !== null ? /* @__PURE__ */ jsxRuntime.jsx(StaticPieceLayer, { board: fallbackBoard, orientation, pieces }) : null,
|
|
2827
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2828
|
+
AnimationRunner,
|
|
2829
|
+
{
|
|
2830
|
+
model: game,
|
|
2831
|
+
containerRef,
|
|
2832
|
+
orientation,
|
|
2833
|
+
...animation !== void 0 ? { options: animation } : {},
|
|
2834
|
+
runtime: animationRuntime
|
|
2835
|
+
}
|
|
2836
|
+
) : null,
|
|
2837
|
+
/* @__PURE__ */ jsxRuntime.jsx(DragLayer, { ref: dragLayerRef, pieces }),
|
|
2838
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2839
|
+
ArrowsLayer,
|
|
2840
|
+
{
|
|
2841
|
+
ref: arrowsLayerRef,
|
|
2842
|
+
model: game,
|
|
2843
|
+
orientation,
|
|
2844
|
+
palette
|
|
2845
|
+
}
|
|
2846
|
+
) : null,
|
|
2847
|
+
showCoordinates ? /* @__PURE__ */ jsxRuntime.jsx(Coordinates, { orientation, ranksPosition }) : null,
|
|
2848
|
+
/* @__PURE__ */ jsxRuntime.jsx(IllegalFlashLayer, { at: flashSquare, orientation }),
|
|
2849
|
+
game !== null ? /* @__PURE__ */ jsxRuntime.jsx(LiveRegion, { model: game }) : null,
|
|
2850
|
+
pendingPromotion !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2851
|
+
PromotionOverlay,
|
|
2852
|
+
{
|
|
2853
|
+
from: pendingPromotion.from,
|
|
2854
|
+
to: pendingPromotion.to,
|
|
2855
|
+
color: pendingPromotion.color,
|
|
2856
|
+
orientation,
|
|
2857
|
+
pieces,
|
|
2858
|
+
onSelect: pendingPromotion.resolve,
|
|
2859
|
+
onCancel: pendingPromotion.cancel,
|
|
2860
|
+
getPromotionPieceAriaLabel
|
|
2861
|
+
}
|
|
2862
|
+
) : null
|
|
2863
|
+
]
|
|
2864
|
+
}
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
function useChessGame(options = {}) {
|
|
2868
|
+
const { fen, ...modelOptions } = options;
|
|
2869
|
+
const [initialModelOptions] = react.useState(() => modelOptions);
|
|
2870
|
+
const [model, setModel] = react.useState(() => {
|
|
2871
|
+
const adapter = chunkUUTEFNFO_cjs.createGigachessAdapter(fen);
|
|
2872
|
+
return chunkUUTEFNFO_cjs.createBoardModel(adapter, modelOptions);
|
|
2873
|
+
});
|
|
2874
|
+
const prevFenRef = react.useRef(fen);
|
|
2875
|
+
react.useEffect(() => {
|
|
2876
|
+
if (prevFenRef.current !== fen) {
|
|
2877
|
+
prevFenRef.current = fen;
|
|
2878
|
+
const adapter = chunkUUTEFNFO_cjs.createGigachessAdapter(fen);
|
|
2879
|
+
const nextModel = chunkUUTEFNFO_cjs.createBoardModel(adapter, initialModelOptions);
|
|
2880
|
+
setModel((prev) => {
|
|
2881
|
+
prev.dispose();
|
|
2882
|
+
return nextModel;
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
}, [fen, initialModelOptions]);
|
|
2886
|
+
react.useEffect(() => {
|
|
2887
|
+
return () => {
|
|
2888
|
+
model.dispose();
|
|
2889
|
+
};
|
|
2890
|
+
}, [model]);
|
|
2891
|
+
return model;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/index.ts
|
|
2895
|
+
var PACKAGE_VERSION = "0.1.0-alpha";
|
|
2896
|
+
|
|
2897
|
+
Object.defineProperty(exports, "Color", {
|
|
2898
|
+
enumerable: true,
|
|
2899
|
+
get: function () { return chunkUUTEFNFO_cjs.Color; }
|
|
2900
|
+
});
|
|
2901
|
+
Object.defineProperty(exports, "PieceType", {
|
|
2902
|
+
enumerable: true,
|
|
2903
|
+
get: function () { return chunkUUTEFNFO_cjs.PieceType; }
|
|
2904
|
+
});
|
|
2905
|
+
Object.defineProperty(exports, "createBoardModel", {
|
|
2906
|
+
enumerable: true,
|
|
2907
|
+
get: function () { return chunkUUTEFNFO_cjs.createBoardModel; }
|
|
2908
|
+
});
|
|
2909
|
+
Object.defineProperty(exports, "createGigachessAdapter", {
|
|
2910
|
+
enumerable: true,
|
|
2911
|
+
get: function () { return chunkUUTEFNFO_cjs.createGigachessAdapter; }
|
|
2912
|
+
});
|
|
2913
|
+
Object.defineProperty(exports, "packMove", {
|
|
2914
|
+
enumerable: true,
|
|
2915
|
+
get: function () { return chunkUUTEFNFO_cjs.packMove; }
|
|
2916
|
+
});
|
|
2917
|
+
Object.defineProperty(exports, "preloadGigachessAdapter", {
|
|
2918
|
+
enumerable: true,
|
|
2919
|
+
get: function () { return chunkUUTEFNFO_cjs.preloadGigachessAdapter; }
|
|
2920
|
+
});
|
|
2921
|
+
Object.defineProperty(exports, "unpackMove", {
|
|
2922
|
+
enumerable: true,
|
|
2923
|
+
get: function () { return chunkUUTEFNFO_cjs.unpackMove; }
|
|
2924
|
+
});
|
|
2925
|
+
Object.defineProperty(exports, "CSS_VARS", {
|
|
2926
|
+
enumerable: true,
|
|
2927
|
+
get: function () { return chunkI4BQGFP5_cjs.CSS_VARS; }
|
|
2928
|
+
});
|
|
2929
|
+
Object.defineProperty(exports, "DEFAULT_ILLEGAL_FLASH_TINT", {
|
|
2930
|
+
enumerable: true,
|
|
2931
|
+
get: function () { return chunkI4BQGFP5_cjs.DEFAULT_ILLEGAL_FLASH_TINT; }
|
|
2932
|
+
});
|
|
2933
|
+
Object.defineProperty(exports, "DEFAULT_PREMOVE_TINT", {
|
|
2934
|
+
enumerable: true,
|
|
2935
|
+
get: function () { return chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT; }
|
|
2936
|
+
});
|
|
2937
|
+
Object.defineProperty(exports, "defaultArrowColors", {
|
|
2938
|
+
enumerable: true,
|
|
2939
|
+
get: function () { return chunkI4BQGFP5_cjs.defaultArrowColors; }
|
|
2940
|
+
});
|
|
2941
|
+
Object.defineProperty(exports, "defaultPieces", {
|
|
2942
|
+
enumerable: true,
|
|
2943
|
+
get: function () { return chunkI4BQGFP5_cjs.defaultPieces; }
|
|
2944
|
+
});
|
|
2945
|
+
Object.defineProperty(exports, "defaultTheme", {
|
|
2946
|
+
enumerable: true,
|
|
2947
|
+
get: function () { return chunkI4BQGFP5_cjs.defaultTheme; }
|
|
2948
|
+
});
|
|
2949
|
+
exports.AnimationRunner = AnimationRunner;
|
|
2950
|
+
exports.Chessboard = Chessboard;
|
|
2951
|
+
exports.DEFAULT_MOVE_HAPTIC_PATTERNS = DEFAULT_MOVE_HAPTIC_PATTERNS;
|
|
2952
|
+
exports.DEFAULT_MOVE_SOUND_SOURCES = DEFAULT_MOVE_SOUND_SOURCES;
|
|
2953
|
+
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
|
2954
|
+
exports.classifyMoveFeedback = classifyMoveFeedback;
|
|
2955
|
+
exports.getPointAtSquareCentre = getPointAtSquareCentre;
|
|
2956
|
+
exports.getSquareAtPoint = getSquareAtPoint;
|
|
2957
|
+
exports.triggerHaptic = triggerHaptic;
|
|
2958
|
+
exports.triggerMoveHaptic = triggerMoveHaptic;
|
|
2959
|
+
exports.useArrowGesture = useArrowGesture;
|
|
2960
|
+
exports.useBoardSlice = useBoardSlice;
|
|
2961
|
+
exports.useBoardSnapshot = useBoardSnapshot;
|
|
2962
|
+
exports.useChessGame = useChessGame;
|
|
2963
|
+
exports.useClickToMove = useClickToMove;
|
|
2964
|
+
exports.useDrag = useDrag;
|
|
2965
|
+
exports.useHoverSquare = useHoverSquare;
|
|
2966
|
+
exports.useKeyboardNav = useKeyboardNav;
|
|
2967
|
+
exports.useMoveHaptics = useMoveHaptics;
|
|
2968
|
+
exports.useMoveSound = useMoveSound;
|
|
2969
|
+
exports.useSquareCell = useSquareCell;
|
|
2970
|
+
exports.warmupHaptics = warmupHaptics;
|
|
2971
|
+
//# sourceMappingURL=index.cjs.map
|
|
2972
|
+
//# sourceMappingURL=index.cjs.map
|