react-native-chess-kit 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/lib/commonjs/board-annotations.js +8 -8
- package/lib/commonjs/board-arrows.js +7 -7
- package/lib/commonjs/board-background.js +5 -5
- package/lib/commonjs/board-coordinates.js +78 -11
- package/lib/commonjs/board-coordinates.js.map +1 -1
- package/lib/commonjs/board-drag-ghost.js +10 -10
- package/lib/commonjs/board-highlights.js +15 -15
- package/lib/commonjs/board-legal-dots.js +5 -5
- package/lib/commonjs/board-piece.js +25 -25
- package/lib/commonjs/board-pieces.js +6 -6
- package/lib/commonjs/board.js +76 -35
- package/lib/commonjs/board.js.map +1 -1
- package/lib/commonjs/constants.js +4 -1
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/promotion-picker.js +8 -8
- package/lib/commonjs/static-board.js +51 -16
- package/lib/commonjs/static-board.js.map +1 -1
- package/lib/commonjs/use-board-gesture.js +52 -33
- package/lib/commonjs/use-board-gesture.js.map +1 -1
- package/lib/commonjs/use-board-pieces.js +15 -15
- package/lib/commonjs/use-board-state.js +8 -8
- package/lib/commonjs/use-premove.js +12 -12
- package/lib/module/board-annotations.js +8 -8
- package/lib/module/board-arrows.js +7 -7
- package/lib/module/board-background.js +5 -5
- package/lib/module/board-coordinates.js +79 -12
- package/lib/module/board-coordinates.js.map +1 -1
- package/lib/module/board-drag-ghost.js +10 -10
- package/lib/module/board-highlights.js +15 -15
- package/lib/module/board-legal-dots.js +5 -5
- package/lib/module/board-piece.js +25 -25
- package/lib/module/board-pieces.js +6 -6
- package/lib/module/board.js +77 -36
- package/lib/module/board.js.map +1 -1
- package/lib/module/constants.js +3 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/promotion-picker.js +8 -8
- package/lib/module/static-board.js +52 -17
- package/lib/module/static-board.js.map +1 -1
- package/lib/module/use-board-gesture.js +52 -33
- package/lib/module/use-board-gesture.js.map +1 -1
- package/lib/module/use-board-pieces.js +15 -15
- package/lib/module/use-board-state.js +8 -8
- package/lib/module/use-premove.js +12 -12
- package/lib/typescript/board-coordinates.d.ts +10 -4
- package/lib/typescript/board-coordinates.d.ts.map +1 -1
- package/lib/typescript/board.d.ts.map +1 -1
- package/lib/typescript/constants.d.ts +2 -0
- package/lib/typescript/constants.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/static-board.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +23 -3
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/use-board-gesture.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/board-annotations.tsx +147 -147
- package/src/board-arrows.tsx +197 -197
- package/src/board-background.tsx +46 -46
- package/src/board-coordinates.tsx +192 -98
- package/src/board-drag-ghost.tsx +132 -132
- package/src/board-highlights.tsx +226 -226
- package/src/board-legal-dots.tsx +73 -73
- package/src/board-piece.tsx +160 -160
- package/src/board-pieces.tsx +63 -63
- package/src/board.tsx +685 -641
- package/src/constants.ts +103 -100
- package/src/index.ts +101 -100
- package/src/pieces/default-pieces.tsx +383 -383
- package/src/pieces/index.ts +1 -1
- package/src/promotion-picker.tsx +147 -147
- package/src/static-board.tsx +187 -150
- package/src/themes.ts +129 -129
- package/src/types.ts +373 -352
- package/src/use-board-gesture.ts +429 -412
- package/src/use-board-pieces.ts +158 -158
- package/src/use-board-state.ts +111 -111
- package/src/use-premove.ts +59 -59
package/src/board.tsx
CHANGED
|
@@ -1,641 +1,685 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
forwardRef,
|
|
3
|
-
useState,
|
|
4
|
-
useCallback,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
useEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useRef,
|
|
9
|
-
} from 'react';
|
|
10
|
-
import { View, type LayoutChangeEvent } from 'react-native';
|
|
11
|
-
import { GestureDetector } from 'react-native-gesture-handler';
|
|
12
|
-
import Animated, {
|
|
13
|
-
useSharedValue,
|
|
14
|
-
useAnimatedStyle,
|
|
15
|
-
withTiming,
|
|
16
|
-
FadeOut,
|
|
17
|
-
} from 'react-native-reanimated';
|
|
18
|
-
|
|
19
|
-
import type {
|
|
20
|
-
BoardRef,
|
|
21
|
-
BoardProps,
|
|
22
|
-
LegalMoveTarget,
|
|
23
|
-
HighlightData,
|
|
24
|
-
PieceCode,
|
|
25
|
-
PromotionPiece,
|
|
26
|
-
} from './types';
|
|
27
|
-
import {
|
|
28
|
-
DEFAULT_BOARD_COLORS,
|
|
29
|
-
DEFAULT_MOVE_DURATION,
|
|
30
|
-
DEFAULT_LAST_MOVE_COLOR,
|
|
31
|
-
DEFAULT_CHECK_COLOR,
|
|
32
|
-
DEFAULT_SELECTED_COLOR,
|
|
33
|
-
DEFAULT_PREMOVE_COLOR,
|
|
34
|
-
DEFAULT_DRAG_TARGET_COLOR,
|
|
35
|
-
CAPTURE_FADE_DURATION,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* -
|
|
101
|
-
* - ~
|
|
102
|
-
* -
|
|
103
|
-
* - 0
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
() => ({
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
setSelectedSquare(null);
|
|
506
|
-
setLegalMoves([]);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
{
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
{
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
)}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { View, type LayoutChangeEvent } from 'react-native';
|
|
11
|
+
import { GestureDetector } from 'react-native-gesture-handler';
|
|
12
|
+
import Animated, {
|
|
13
|
+
useSharedValue,
|
|
14
|
+
useAnimatedStyle,
|
|
15
|
+
withTiming,
|
|
16
|
+
FadeOut,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
BoardRef,
|
|
21
|
+
BoardProps,
|
|
22
|
+
LegalMoveTarget,
|
|
23
|
+
HighlightData,
|
|
24
|
+
PieceCode,
|
|
25
|
+
PromotionPiece,
|
|
26
|
+
} from './types';
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_BOARD_COLORS,
|
|
29
|
+
DEFAULT_MOVE_DURATION,
|
|
30
|
+
DEFAULT_LAST_MOVE_COLOR,
|
|
31
|
+
DEFAULT_CHECK_COLOR,
|
|
32
|
+
DEFAULT_SELECTED_COLOR,
|
|
33
|
+
DEFAULT_PREMOVE_COLOR,
|
|
34
|
+
DEFAULT_DRAG_TARGET_COLOR,
|
|
35
|
+
CAPTURE_FADE_DURATION,
|
|
36
|
+
COORDINATE_GUTTER_SCALE,
|
|
37
|
+
} from './constants';
|
|
38
|
+
import { DefaultPieceSet } from './pieces';
|
|
39
|
+
import { useBoardPieces } from './use-board-pieces';
|
|
40
|
+
import { useBoardState } from './use-board-state';
|
|
41
|
+
import { useBoardGesture } from './use-board-gesture';
|
|
42
|
+
import { usePremove } from './use-premove';
|
|
43
|
+
import { BoardBackground } from './board-background';
|
|
44
|
+
import { BoardCoordinates } from './board-coordinates';
|
|
45
|
+
import { BoardHighlights, DragTargetHighlight } from './board-highlights';
|
|
46
|
+
import { BoardLegalDots } from './board-legal-dots';
|
|
47
|
+
import { BoardPiecesLayer } from './board-pieces';
|
|
48
|
+
import { BoardDragGhost } from './board-drag-ghost';
|
|
49
|
+
import { BoardArrows } from './board-arrows';
|
|
50
|
+
import { BoardAnnotations } from './board-annotations';
|
|
51
|
+
import { PromotionPicker } from './promotion-picker';
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Check detection helper
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Find the king square for the side currently in check.
|
|
59
|
+
* Returns null if not in check.
|
|
60
|
+
*/
|
|
61
|
+
function detectCheckSquare(
|
|
62
|
+
fen: string,
|
|
63
|
+
isInCheck: () => boolean,
|
|
64
|
+
getTurn: () => 'w' | 'b',
|
|
65
|
+
): string | null {
|
|
66
|
+
if (!isInCheck()) return null;
|
|
67
|
+
|
|
68
|
+
const turn = getTurn();
|
|
69
|
+
const kingChar = turn === 'w' ? 'K' : 'k';
|
|
70
|
+
const placement = fen.split(' ')[0];
|
|
71
|
+
const ranks = placement.split('/');
|
|
72
|
+
|
|
73
|
+
for (let rankIdx = 0; rankIdx < ranks.length; rankIdx++) {
|
|
74
|
+
const rank = ranks[rankIdx]!;
|
|
75
|
+
let fileIdx = 0;
|
|
76
|
+
for (const char of rank) {
|
|
77
|
+
if (char >= '1' && char <= '8') {
|
|
78
|
+
fileIdx += parseInt(char, 10);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === kingChar) {
|
|
82
|
+
const files = 'abcdefgh';
|
|
83
|
+
return `${files[fileIdx]}${8 - rankIdx}`;
|
|
84
|
+
}
|
|
85
|
+
fileIdx++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Board component
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* High-performance custom chess board built on Reanimated + Gesture Handler.
|
|
98
|
+
*
|
|
99
|
+
* Architecture:
|
|
100
|
+
* - 1 gesture handler (vs 32 in typical implementations)
|
|
101
|
+
* - ~40 components mounted (vs ~281)
|
|
102
|
+
* - ~75 native views (vs ~470)
|
|
103
|
+
* - 0 React Context providers
|
|
104
|
+
* - 0 re-renders during drag (pure worklet — only 2 shared value writes per frame)
|
|
105
|
+
*
|
|
106
|
+
* v0.2.0 layer stack (10 layers):
|
|
107
|
+
* 1. BoardBackground (64 squares)
|
|
108
|
+
* 2. BoardCoordinates (a-h, 1-8)
|
|
109
|
+
* 3. BoardHighlights (last move, check, selected, premove, custom, imperative)
|
|
110
|
+
* 4. DragTargetHighlight (animated, worklet-driven)
|
|
111
|
+
* 5. BoardLegalDots (legal move indicators)
|
|
112
|
+
* 6. BoardPiecesLayer (all pieces)
|
|
113
|
+
* 7. BoardArrows (SVG arrows + circles)
|
|
114
|
+
* 8. BoardAnnotations (text badges)
|
|
115
|
+
* 9. BoardDragGhost (floating piece)
|
|
116
|
+
* 10. PromotionPicker (modal, conditional)
|
|
117
|
+
*/
|
|
118
|
+
export const Board = forwardRef<BoardRef, BoardProps>(function Board(
|
|
119
|
+
{
|
|
120
|
+
fen,
|
|
121
|
+
orientation,
|
|
122
|
+
|
|
123
|
+
// Layout
|
|
124
|
+
boardSize: boardSizeProp,
|
|
125
|
+
|
|
126
|
+
// Interaction
|
|
127
|
+
gestureEnabled = true,
|
|
128
|
+
player = 'both',
|
|
129
|
+
moveMethod = 'both',
|
|
130
|
+
showLegalMoves = true,
|
|
131
|
+
premovesEnabled = false,
|
|
132
|
+
|
|
133
|
+
// Appearance
|
|
134
|
+
colors,
|
|
135
|
+
coordinatePosition: coordinatePositionProp,
|
|
136
|
+
withLetters: withLettersProp,
|
|
137
|
+
withNumbers: withNumbersProp,
|
|
138
|
+
renderPiece,
|
|
139
|
+
pieceSet,
|
|
140
|
+
|
|
141
|
+
// Overlays
|
|
142
|
+
lastMove,
|
|
143
|
+
highlights,
|
|
144
|
+
arrows,
|
|
145
|
+
shapes,
|
|
146
|
+
annotations,
|
|
147
|
+
showDragTarget = true,
|
|
148
|
+
|
|
149
|
+
// Overlay colors
|
|
150
|
+
lastMoveColor = DEFAULT_LAST_MOVE_COLOR,
|
|
151
|
+
checkHighlightColor = DEFAULT_CHECK_COLOR,
|
|
152
|
+
selectedSquareColor = DEFAULT_SELECTED_COLOR,
|
|
153
|
+
premoveColor = DEFAULT_PREMOVE_COLOR,
|
|
154
|
+
dragTargetColor = DEFAULT_DRAG_TARGET_COLOR,
|
|
155
|
+
|
|
156
|
+
// Animation
|
|
157
|
+
moveDuration = DEFAULT_MOVE_DURATION,
|
|
158
|
+
animationConfig,
|
|
159
|
+
animateFlip = true,
|
|
160
|
+
pieceExitAnimation,
|
|
161
|
+
|
|
162
|
+
// Promotion
|
|
163
|
+
onPromotion,
|
|
164
|
+
|
|
165
|
+
// Callbacks
|
|
166
|
+
onMove,
|
|
167
|
+
onPieceClick,
|
|
168
|
+
onSquareClick,
|
|
169
|
+
onPieceDragBegin,
|
|
170
|
+
onPieceDragEnd,
|
|
171
|
+
onSquareLongPress,
|
|
172
|
+
onPremove,
|
|
173
|
+
onHaptic,
|
|
174
|
+
},
|
|
175
|
+
ref,
|
|
176
|
+
) {
|
|
177
|
+
// --- Auto-sizing via onLayout when boardSize not provided ---
|
|
178
|
+
const [measuredSize, setMeasuredSize] = useState(0);
|
|
179
|
+
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
|
180
|
+
const { width, height } = e.nativeEvent.layout;
|
|
181
|
+
setMeasuredSize(Math.min(width, height));
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
const outerSize = boardSizeProp ?? measuredSize;
|
|
185
|
+
const boardColors = colors ?? DEFAULT_BOARD_COLORS;
|
|
186
|
+
|
|
187
|
+
// Resolve coordinate position: new prop takes precedence over legacy booleans
|
|
188
|
+
const coordinatePosition = coordinatePositionProp
|
|
189
|
+
?? (withLettersProp === false && withNumbersProp === false ? 'none' : 'inside');
|
|
190
|
+
const isOutside = coordinatePosition === 'outside';
|
|
191
|
+
const isCoordVisible = coordinatePosition !== 'none';
|
|
192
|
+
|
|
193
|
+
// When outside: reserve gutter space; inner board = outer minus gutters
|
|
194
|
+
const gutterWidth = isOutside ? Math.round((outerSize / 8) * COORDINATE_GUTTER_SCALE) : 0;
|
|
195
|
+
const boardSize = isOutside ? outerSize - gutterWidth : outerSize;
|
|
196
|
+
const squareSize = boardSize / 8;
|
|
197
|
+
|
|
198
|
+
// Resolve piece exit animation: null = disabled, undefined = default FadeOut
|
|
199
|
+
const resolvedPieceExitAnimation =
|
|
200
|
+
pieceExitAnimation === null
|
|
201
|
+
? undefined
|
|
202
|
+
: (pieceExitAnimation ?? FadeOut.duration(CAPTURE_FADE_DURATION));
|
|
203
|
+
|
|
204
|
+
// --- Board flip animation ---
|
|
205
|
+
const flipRotation = useSharedValue(orientation === 'black' ? 180 : 0);
|
|
206
|
+
const prevOrientationRef = useRef(orientation);
|
|
207
|
+
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
if (prevOrientationRef.current !== orientation) {
|
|
210
|
+
prevOrientationRef.current = orientation;
|
|
211
|
+
if (animateFlip) {
|
|
212
|
+
flipRotation.value = withTiming(
|
|
213
|
+
orientation === 'black' ? 180 : 0,
|
|
214
|
+
{ duration: 300 },
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
flipRotation.value = orientation === 'black' ? 180 : 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}, [orientation, animateFlip, flipRotation]);
|
|
221
|
+
|
|
222
|
+
// Note: We don't actually rotate the board view because all layers already
|
|
223
|
+
// handle orientation via squareToXY coordinate math. The flip animation is
|
|
224
|
+
// a visual effect only — the rotation shared value is available for consumers
|
|
225
|
+
// who want to add a rotation transition effect.
|
|
226
|
+
|
|
227
|
+
// --- Internal FEN state ---
|
|
228
|
+
// The Board owns a private FEN that drives piece rendering.
|
|
229
|
+
// It starts from the parent prop and stays in sync via useEffect,
|
|
230
|
+
// but can temporarily diverge when the library applies a user move
|
|
231
|
+
// before the parent validates it (enables Chess.com-style "show move
|
|
232
|
+
// then revert" behavior via undo()).
|
|
233
|
+
const [internalFen, setInternalFen] = useState(fen);
|
|
234
|
+
|
|
235
|
+
// --- Chess.js for legal move validation + internal state ---
|
|
236
|
+
const boardState = useBoardState(fen);
|
|
237
|
+
|
|
238
|
+
// Sync internal FEN + chess.js when parent changes the prop FEN.
|
|
239
|
+
// This covers: accepted moves (parent updates FEN), board reset,
|
|
240
|
+
// and opponent programmatic moves.
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
setInternalFen(fen);
|
|
243
|
+
boardState.loadFen(fen);
|
|
244
|
+
}, [fen, boardState]);
|
|
245
|
+
|
|
246
|
+
// --- Piece data from internal FEN ---
|
|
247
|
+
const pieces = useBoardPieces(internalFen);
|
|
248
|
+
|
|
249
|
+
// --- Check detection ---
|
|
250
|
+
// Detect if the side to move is in check by parsing the FEN.
|
|
251
|
+
// chess.js isInCheck() isn't exposed through boardState, so we use
|
|
252
|
+
// a simple heuristic: if the FEN has the king of the side to move
|
|
253
|
+
// attacked, highlight it.
|
|
254
|
+
const checkSquareState = useMemo(() => {
|
|
255
|
+
try {
|
|
256
|
+
return detectCheckSquare(
|
|
257
|
+
internalFen,
|
|
258
|
+
() => boardState.isInCheck(),
|
|
259
|
+
boardState.getTurn,
|
|
260
|
+
);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}, [internalFen, boardState]);
|
|
265
|
+
|
|
266
|
+
// --- Selection state ---
|
|
267
|
+
const [selectedSquare, setSelectedSquare] = useState<string | null>(null);
|
|
268
|
+
const [legalMoves, setLegalMoves] = useState<LegalMoveTarget[]>([]);
|
|
269
|
+
|
|
270
|
+
// --- Imperative highlights ---
|
|
271
|
+
const [imperativeHighlights, setImperativeHighlights] = useState<HighlightData[]>([]);
|
|
272
|
+
|
|
273
|
+
// --- Premove state ---
|
|
274
|
+
const { premove, setPremove, clearPremove, consumePremove } = usePremove();
|
|
275
|
+
|
|
276
|
+
// --- Promotion state ---
|
|
277
|
+
const [promotionState, setPromotionState] = useState<{
|
|
278
|
+
from: string;
|
|
279
|
+
to: string;
|
|
280
|
+
color: 'w' | 'b';
|
|
281
|
+
} | null>(null);
|
|
282
|
+
|
|
283
|
+
// --- Resolve piece renderer: renderPiece > pieceSet > DefaultPieceSet ---
|
|
284
|
+
const resolvedRenderer = useMemo(() => {
|
|
285
|
+
if (renderPiece) return renderPiece;
|
|
286
|
+
const set = pieceSet ?? DefaultPieceSet;
|
|
287
|
+
return (code: string, size: number) => {
|
|
288
|
+
const renderer = set[code as PieceCode];
|
|
289
|
+
if (renderer) return renderer(size);
|
|
290
|
+
return <View style={{ width: size, height: size }} />;
|
|
291
|
+
};
|
|
292
|
+
}, [renderPiece, pieceSet]);
|
|
293
|
+
|
|
294
|
+
// --- Promotion detection ---
|
|
295
|
+
const isPromotionMove = useCallback(
|
|
296
|
+
(from: string, to: string): boolean => {
|
|
297
|
+
const piece = pieces.find((p) => p.square === from);
|
|
298
|
+
if (!piece) return false;
|
|
299
|
+
// Must be a pawn
|
|
300
|
+
if (piece.code !== 'wp' && piece.code !== 'bp') return false;
|
|
301
|
+
// Must be moving to the last rank
|
|
302
|
+
const toRank = to[1];
|
|
303
|
+
if (piece.color === 'w' && toRank === '8') return true;
|
|
304
|
+
if (piece.color === 'b' && toRank === '1') return true;
|
|
305
|
+
return false;
|
|
306
|
+
},
|
|
307
|
+
[pieces],
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// --- Gesture callbacks ---
|
|
311
|
+
const handlePieceSelected = useCallback(
|
|
312
|
+
(square: string) => {
|
|
313
|
+
setSelectedSquare(square);
|
|
314
|
+
if (showLegalMoves) {
|
|
315
|
+
setLegalMoves(boardState.getLegalMoves(square));
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
[showLegalMoves, boardState],
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const handleSelectionCleared = useCallback(() => {
|
|
322
|
+
setSelectedSquare(null);
|
|
323
|
+
setLegalMoves([]);
|
|
324
|
+
}, []);
|
|
325
|
+
|
|
326
|
+
const handlePieceMoved = useCallback(
|
|
327
|
+
async (from: string, to: string) => {
|
|
328
|
+
// Clear selection and legal dots
|
|
329
|
+
setSelectedSquare(null);
|
|
330
|
+
setLegalMoves([]);
|
|
331
|
+
|
|
332
|
+
// Check for promotion
|
|
333
|
+
if (isPromotionMove(from, to)) {
|
|
334
|
+
if (onPromotion) {
|
|
335
|
+
const piece = pieces.find((p) => p.square === from);
|
|
336
|
+
const color = piece?.color ?? 'w';
|
|
337
|
+
setPromotionState({ from, to, color });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Auto-promote to queen
|
|
341
|
+
const result = boardState.applyMove(from, to, 'q');
|
|
342
|
+
if (result.applied && result.fen) {
|
|
343
|
+
setInternalFen(result.fen);
|
|
344
|
+
}
|
|
345
|
+
onMove?.({ from, to });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Apply the move visually FIRST (Chess.com-style: show the move,
|
|
350
|
+
// then let the parent validate). The parent can call undo() to
|
|
351
|
+
// revert if the move is rejected.
|
|
352
|
+
const result = boardState.applyMove(from, to);
|
|
353
|
+
if (result.applied && result.fen) {
|
|
354
|
+
setInternalFen(result.fen);
|
|
355
|
+
onMove?.({ from, to });
|
|
356
|
+
}
|
|
357
|
+
// If chess.js rejected the move (truly illegal), do nothing —
|
|
358
|
+
// piece stays at its original square.
|
|
359
|
+
},
|
|
360
|
+
[onMove, onPromotion, isPromotionMove, pieces, boardState],
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// --- Promotion picker handlers ---
|
|
364
|
+
const handlePromotionSelect = useCallback(
|
|
365
|
+
async (piece: PromotionPiece) => {
|
|
366
|
+
if (!promotionState) return;
|
|
367
|
+
const { from, to } = promotionState;
|
|
368
|
+
setPromotionState(null);
|
|
369
|
+
|
|
370
|
+
const promo = piece.toLowerCase();
|
|
371
|
+
|
|
372
|
+
if (onPromotion) {
|
|
373
|
+
try {
|
|
374
|
+
const choice = await onPromotion(from, to);
|
|
375
|
+
const result = boardState.applyMove(from, to, choice);
|
|
376
|
+
if (result.applied && result.fen) {
|
|
377
|
+
setInternalFen(result.fen);
|
|
378
|
+
}
|
|
379
|
+
onMove?.({ from, to });
|
|
380
|
+
} catch {
|
|
381
|
+
// Promotion cancelled — piece stays at origin
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
const result = boardState.applyMove(from, to, promo);
|
|
385
|
+
if (result.applied && result.fen) {
|
|
386
|
+
setInternalFen(result.fen);
|
|
387
|
+
}
|
|
388
|
+
onMove?.({ from, to });
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
[promotionState, onPromotion, onMove, boardState],
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const handlePromotionCancel = useCallback(() => {
|
|
395
|
+
setPromotionState(null);
|
|
396
|
+
}, []);
|
|
397
|
+
|
|
398
|
+
// --- Premove handling ---
|
|
399
|
+
const handlePremoveSet = useCallback(
|
|
400
|
+
(pm: { from: string; to: string }) => {
|
|
401
|
+
setPremove(pm);
|
|
402
|
+
onPremove?.(pm);
|
|
403
|
+
onHaptic?.('select');
|
|
404
|
+
},
|
|
405
|
+
[setPremove, onPremove, onHaptic],
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Execute premove when turn changes
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
if (!premovesEnabled || !premove) return;
|
|
411
|
+
|
|
412
|
+
const turn = boardState.getTurn();
|
|
413
|
+
// Check if it's now the premover's turn
|
|
414
|
+
const premovePiece = pieces.find((p) => p.square === premove.from);
|
|
415
|
+
if (!premovePiece) {
|
|
416
|
+
clearPremove();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (premovePiece.color === turn) {
|
|
421
|
+
const consumed = consumePremove();
|
|
422
|
+
if (consumed) {
|
|
423
|
+
// Try to execute the premove
|
|
424
|
+
const result = boardState.applyMove(consumed.from, consumed.to, consumed.promotion);
|
|
425
|
+
if (result.applied && result.fen) {
|
|
426
|
+
setInternalFen(result.fen);
|
|
427
|
+
onMove?.({ from: consumed.from, to: consumed.to });
|
|
428
|
+
onHaptic?.('move');
|
|
429
|
+
} else {
|
|
430
|
+
// Premove was illegal — discard silently
|
|
431
|
+
onHaptic?.('error');
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}, [fen, premovesEnabled, premove, pieces, boardState, consumePremove, clearPremove, onMove, onHaptic]);
|
|
436
|
+
|
|
437
|
+
// --- Rich callbacks ref (stable, for gesture hook) ---
|
|
438
|
+
const richCallbacks = useMemo(
|
|
439
|
+
() => ({
|
|
440
|
+
onPieceClick,
|
|
441
|
+
onSquareClick,
|
|
442
|
+
onPieceDragBegin,
|
|
443
|
+
onPieceDragEnd,
|
|
444
|
+
onSquareLongPress,
|
|
445
|
+
onHaptic,
|
|
446
|
+
}),
|
|
447
|
+
[onPieceClick, onSquareClick, onPieceDragBegin, onPieceDragEnd, onSquareLongPress, onHaptic],
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const premoveCallbacks = useMemo(
|
|
451
|
+
() => ({
|
|
452
|
+
onPremoveSet: handlePremoveSet,
|
|
453
|
+
}),
|
|
454
|
+
[handlePremoveSet],
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
// --- Single centralized gesture ---
|
|
458
|
+
const { gesture, gestureState } = useBoardGesture({
|
|
459
|
+
squareSize,
|
|
460
|
+
orientation,
|
|
461
|
+
gestureEnabled,
|
|
462
|
+
player,
|
|
463
|
+
moveMethod,
|
|
464
|
+
pieces,
|
|
465
|
+
callbacks: {
|
|
466
|
+
onPieceSelected: handlePieceSelected,
|
|
467
|
+
onPieceMoved: handlePieceMoved,
|
|
468
|
+
onSelectionCleared: handleSelectionCleared,
|
|
469
|
+
},
|
|
470
|
+
richCallbacks,
|
|
471
|
+
premoveCallbacks,
|
|
472
|
+
premovesEnabled,
|
|
473
|
+
selectedSquare,
|
|
474
|
+
legalMoves,
|
|
475
|
+
currentTurn: boardState.getTurn(),
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// --- Imperative ref ---
|
|
479
|
+
useImperativeHandle(ref, () => ({
|
|
480
|
+
move: (moveArg) => {
|
|
481
|
+
const result = boardState.applyMove(moveArg.from, moveArg.to, moveArg.promotion);
|
|
482
|
+
if (result.applied && result.fen) {
|
|
483
|
+
setInternalFen(result.fen);
|
|
484
|
+
}
|
|
485
|
+
// Resolve after the piece animation completes. The moveDuration prop
|
|
486
|
+
// drives BoardPieceView's withTiming, so this matches exactly.
|
|
487
|
+
const duration = moveDuration ?? DEFAULT_MOVE_DURATION;
|
|
488
|
+
return new Promise<void>((resolve) => setTimeout(resolve, duration));
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
highlight: (square, color) => {
|
|
492
|
+
setImperativeHighlights((prev) => {
|
|
493
|
+
const filtered = prev.filter((h) => h.square !== square);
|
|
494
|
+
return [...filtered, { square, color }];
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
clearHighlights: () => {
|
|
499
|
+
setImperativeHighlights([]);
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
resetBoard: (newFen) => {
|
|
503
|
+
boardState.loadFen(newFen);
|
|
504
|
+
setInternalFen(newFen);
|
|
505
|
+
setSelectedSquare(null);
|
|
506
|
+
setLegalMoves([]);
|
|
507
|
+
setImperativeHighlights([]);
|
|
508
|
+
clearPremove();
|
|
509
|
+
setPromotionState(null);
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
undo: () => {
|
|
513
|
+
const prevFen = boardState.undoMove();
|
|
514
|
+
if (prevFen) {
|
|
515
|
+
setInternalFen(prevFen);
|
|
516
|
+
}
|
|
517
|
+
setSelectedSquare(null);
|
|
518
|
+
setLegalMoves([]);
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
clearPremoves: () => {
|
|
522
|
+
clearPremove();
|
|
523
|
+
},
|
|
524
|
+
}));
|
|
525
|
+
|
|
526
|
+
// If no size yet (auto-sizing), render invisible container for measurement
|
|
527
|
+
if (outerSize === 0) {
|
|
528
|
+
return (
|
|
529
|
+
<View style={{ flex: 1, aspectRatio: 1 }} onLayout={handleLayout} />
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Inner board with all interactive layers
|
|
534
|
+
const boardContent = (
|
|
535
|
+
<GestureDetector gesture={gesture}>
|
|
536
|
+
<View
|
|
537
|
+
style={isOutside
|
|
538
|
+
? { width: boardSize, height: boardSize, position: 'absolute', top: 0, right: 0 }
|
|
539
|
+
: { width: boardSize, height: boardSize }}
|
|
540
|
+
onLayout={!isOutside && !boardSizeProp ? handleLayout : undefined}
|
|
541
|
+
accessibilityLabel="Chess board"
|
|
542
|
+
accessibilityRole="adjustable"
|
|
543
|
+
>
|
|
544
|
+
{/* Layer 1: Board background (64 colored squares) */}
|
|
545
|
+
<BoardBackground
|
|
546
|
+
boardSize={boardSize}
|
|
547
|
+
lightColor={boardColors.light}
|
|
548
|
+
darkColor={boardColors.dark}
|
|
549
|
+
/>
|
|
550
|
+
|
|
551
|
+
{/* Layer 2: Inside coordinate labels (when position='inside') */}
|
|
552
|
+
{isCoordVisible && !isOutside && (
|
|
553
|
+
<BoardCoordinates
|
|
554
|
+
boardSize={boardSize}
|
|
555
|
+
orientation={orientation}
|
|
556
|
+
lightColor={boardColors.light}
|
|
557
|
+
darkColor={boardColors.dark}
|
|
558
|
+
withLetters
|
|
559
|
+
withNumbers
|
|
560
|
+
position="inside"
|
|
561
|
+
/>
|
|
562
|
+
)}
|
|
563
|
+
|
|
564
|
+
{/* Layer 3: Square highlights (last move, check, selected, premove, custom, imperative) */}
|
|
565
|
+
<BoardHighlights
|
|
566
|
+
boardSize={boardSize}
|
|
567
|
+
orientation={orientation}
|
|
568
|
+
squareSize={squareSize}
|
|
569
|
+
lastMove={lastMove}
|
|
570
|
+
lastMoveColor={lastMoveColor}
|
|
571
|
+
checkSquare={checkSquareState}
|
|
572
|
+
checkColor={checkHighlightColor}
|
|
573
|
+
selectedSquare={selectedSquare}
|
|
574
|
+
selectedColor={selectedSquareColor}
|
|
575
|
+
premoveSquares={premove ? { from: premove.from, to: premove.to } : null}
|
|
576
|
+
premoveColor={premoveColor}
|
|
577
|
+
highlights={highlights}
|
|
578
|
+
imperativeHighlights={imperativeHighlights}
|
|
579
|
+
/>
|
|
580
|
+
|
|
581
|
+
{/* Layer 4: Drag target highlight (animated, updates during drag) */}
|
|
582
|
+
{showDragTarget && (
|
|
583
|
+
<DragTargetHighlight
|
|
584
|
+
squareSize={squareSize}
|
|
585
|
+
orientation={orientation}
|
|
586
|
+
dragTargetSquare={gestureState.dragTargetSquare}
|
|
587
|
+
color={dragTargetColor}
|
|
588
|
+
/>
|
|
589
|
+
)}
|
|
590
|
+
|
|
591
|
+
{/* Layer 5: Legal move dots (only when a piece is selected) */}
|
|
592
|
+
{showLegalMoves && (
|
|
593
|
+
<BoardLegalDots
|
|
594
|
+
legalMoves={legalMoves}
|
|
595
|
+
squareSize={squareSize}
|
|
596
|
+
orientation={orientation}
|
|
597
|
+
/>
|
|
598
|
+
)}
|
|
599
|
+
|
|
600
|
+
{/* Layer 6: Pieces */}
|
|
601
|
+
<BoardPiecesLayer
|
|
602
|
+
pieces={pieces}
|
|
603
|
+
squareSize={squareSize}
|
|
604
|
+
orientation={orientation}
|
|
605
|
+
moveDuration={moveDuration}
|
|
606
|
+
animationConfig={animationConfig}
|
|
607
|
+
renderPiece={resolvedRenderer}
|
|
608
|
+
activeSquare={gestureState.activeSquare}
|
|
609
|
+
isDragging={gestureState.isDragging}
|
|
610
|
+
exitingAnimation={resolvedPieceExitAnimation}
|
|
611
|
+
/>
|
|
612
|
+
|
|
613
|
+
{/* Layer 7: Arrows + shapes (SVG overlay) */}
|
|
614
|
+
{((arrows && arrows.length > 0) || (shapes && shapes.length > 0)) && (
|
|
615
|
+
<BoardArrows
|
|
616
|
+
boardSize={boardSize}
|
|
617
|
+
orientation={orientation}
|
|
618
|
+
arrows={arrows}
|
|
619
|
+
shapes={shapes}
|
|
620
|
+
/>
|
|
621
|
+
)}
|
|
622
|
+
|
|
623
|
+
{/* Layer 8: Annotations (text badges) */}
|
|
624
|
+
{annotations && annotations.length > 0 && (
|
|
625
|
+
<BoardAnnotations
|
|
626
|
+
boardSize={boardSize}
|
|
627
|
+
orientation={orientation}
|
|
628
|
+
squareSize={squareSize}
|
|
629
|
+
annotations={annotations}
|
|
630
|
+
/>
|
|
631
|
+
)}
|
|
632
|
+
|
|
633
|
+
{/* Layer 9: Drag ghost (single floating piece) */}
|
|
634
|
+
<BoardDragGhost
|
|
635
|
+
squareSize={squareSize}
|
|
636
|
+
isDragging={gestureState.isDragging}
|
|
637
|
+
dragX={gestureState.dragX}
|
|
638
|
+
dragY={gestureState.dragY}
|
|
639
|
+
dragPieceCode={gestureState.dragPieceCode}
|
|
640
|
+
renderPiece={resolvedRenderer}
|
|
641
|
+
/>
|
|
642
|
+
|
|
643
|
+
{/* Layer 10: Promotion picker (conditional) */}
|
|
644
|
+
{promotionState && (
|
|
645
|
+
<PromotionPicker
|
|
646
|
+
square={promotionState.to}
|
|
647
|
+
pieceColor={promotionState.color}
|
|
648
|
+
boardSize={boardSize}
|
|
649
|
+
squareSize={squareSize}
|
|
650
|
+
orientation={orientation}
|
|
651
|
+
renderPiece={resolvedRenderer}
|
|
652
|
+
onSelect={handlePromotionSelect}
|
|
653
|
+
onCancel={handlePromotionCancel}
|
|
654
|
+
/>
|
|
655
|
+
)}
|
|
656
|
+
</View>
|
|
657
|
+
</GestureDetector>
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Outside coordinates: wrap board in outer container with gutter
|
|
661
|
+
if (isOutside) {
|
|
662
|
+
return (
|
|
663
|
+
<View
|
|
664
|
+
style={{ width: outerSize, height: boardSize + gutterWidth }}
|
|
665
|
+
onLayout={boardSizeProp ? undefined : handleLayout}
|
|
666
|
+
>
|
|
667
|
+
{boardContent}
|
|
668
|
+
|
|
669
|
+
{/* Outside coordinate labels in the gutter area */}
|
|
670
|
+
<BoardCoordinates
|
|
671
|
+
boardSize={boardSize}
|
|
672
|
+
orientation={orientation}
|
|
673
|
+
lightColor={boardColors.light}
|
|
674
|
+
darkColor={boardColors.dark}
|
|
675
|
+
withLetters
|
|
676
|
+
withNumbers
|
|
677
|
+
position="outside"
|
|
678
|
+
gutterWidth={gutterWidth}
|
|
679
|
+
/>
|
|
680
|
+
</View>
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return boardContent;
|
|
685
|
+
});
|