react-chess-core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +2 -0
  2. package/dist/features/analysis/core/AnalysisBoardCore.d.ts +8 -2
  3. package/dist/features/chessboard/ChessboardDnDProvider.d.ts +9 -0
  4. package/dist/features/chessboard/boardThemes.d.ts +19 -0
  5. package/dist/features/chessboard/chessboardTheme.d.ts +6 -2
  6. package/dist/features/chessboard/index.d.ts +2 -0
  7. package/dist/features/engine/AnalysisEngineContext.d.ts +22 -0
  8. package/dist/features/engine/index.d.ts +1 -0
  9. package/dist/features/engine/types.d.ts +10 -0
  10. package/dist/features/engine/useAnalysisEngine.d.ts +2 -0
  11. package/dist/features/navigation/PlyNavigation.d.ts +1 -1
  12. package/dist/features/navigation/index.d.ts +2 -0
  13. package/dist/features/navigation/positionKeyboardNav.d.ts +12 -0
  14. package/dist/features/navigation/types.d.ts +5 -0
  15. package/dist/features/navigation/usePositionKeyboardNav.d.ts +12 -0
  16. package/dist/features/training/expectedMoveDrop.d.ts +24 -0
  17. package/dist/features/training/index.d.ts +4 -0
  18. package/dist/features/training/miss/index.d.ts +5 -0
  19. package/dist/features/training/miss/missDisplay.d.ts +16 -0
  20. package/dist/features/training/miss/refutation.d.ts +19 -0
  21. package/dist/features/training/miss/useMissBoard.d.ts +28 -0
  22. package/dist/features/training/miss/useMissRefutation.d.ts +3 -0
  23. package/dist/features/training/miss/useMissSequence.d.ts +10 -0
  24. package/dist/features/training/uciFromDrop.d.ts +3 -0
  25. package/dist/features/training/useBoardRevision.d.ts +9 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.esm.js +2406 -89
  28. package/dist/index.js +2436 -87
  29. package/dist/stories/AnalysisBoard.stories.d.ts +9 -0
  30. package/dist/stories/analysisFixtures.d.ts +5 -0
  31. package/dist/stories/regressions/StockfishAnalysisRegressions.stories.d.ts +7 -0
  32. package/dist/stories/regressions/fixtures.d.ts +17 -0
  33. package/dist/stories/regressions/regressionAnalysisContext.d.ts +6 -0
  34. package/dist/stories/storybookLayout.d.ts +4 -0
  35. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -6,6 +6,80 @@ var reactChessboard = require('react-chessboard');
6
6
  var chess_js = require('chess.js');
7
7
  var reactDom = require('react-dom');
8
8
 
9
+ const BOARD_THEME_IDS = [
10
+ 'classic',
11
+ 'forest',
12
+ 'marine',
13
+ 'marble',
14
+ 'ivory',
15
+ 'ocean',
16
+ 'copper',
17
+ 'bubblegum',
18
+ 'sunset',
19
+ ];
20
+ const DEFAULT_BOARD_THEME = 'classic';
21
+ const BOARD_THEMES = {
22
+ classic: {
23
+ label: 'Classic',
24
+ darkSquare: '#b58863',
25
+ lightSquare: '#f0d9b5',
26
+ },
27
+ forest: {
28
+ label: 'Forest',
29
+ darkSquare: '#769656',
30
+ lightSquare: '#eeeed2',
31
+ },
32
+ marine: {
33
+ label: 'Marine',
34
+ darkSquare: '#5b7c99',
35
+ lightSquare: '#d6e4f0',
36
+ },
37
+ marble: {
38
+ label: 'Marble',
39
+ darkSquare: '#a8a8a8',
40
+ lightSquare: '#ffffff',
41
+ },
42
+ ivory: {
43
+ label: 'Ivory',
44
+ darkSquare: '#c9a66b',
45
+ lightSquare: '#fff8e7',
46
+ },
47
+ ocean: {
48
+ label: 'Ocean',
49
+ darkSquare: '#2d6a7e',
50
+ lightSquare: '#a8dadc',
51
+ },
52
+ copper: {
53
+ label: 'Copper',
54
+ darkSquare: '#9c6644',
55
+ lightSquare: '#f4e4d4',
56
+ },
57
+ bubblegum: {
58
+ label: 'Bubblegum',
59
+ darkSquare: '#c77dff',
60
+ lightSquare: '#ffd6ff',
61
+ },
62
+ sunset: {
63
+ label: 'Sunset',
64
+ darkSquare: '#e76f51',
65
+ lightSquare: '#ffe8d6',
66
+ },
67
+ };
68
+ function isBoardThemeId(value) {
69
+ return (typeof value === 'string' &&
70
+ BOARD_THEME_IDS.includes(value));
71
+ }
72
+ function boardThemeFromLegacyUiTheme(_theme) {
73
+ return DEFAULT_BOARD_THEME;
74
+ }
75
+ function getBoardThemeStyles(boardTheme) {
76
+ const { darkSquare, lightSquare } = BOARD_THEMES[boardTheme];
77
+ return {
78
+ customDarkSquareStyle: { backgroundColor: darkSquare },
79
+ customLightSquareStyle: { backgroundColor: lightSquare },
80
+ };
81
+ }
82
+
9
83
  const ChessboardThemeContext = react.createContext(undefined);
10
84
  const useChessboardTheme = () => {
11
85
  const context = react.useContext(ChessboardThemeContext);
@@ -16,23 +90,1607 @@ const useChessboardTheme = () => {
16
90
  };
17
91
  /** @deprecated Use {@link useChessboardTheme}. */
18
92
  const useTheme = useChessboardTheme;
19
- const getStylesForTheme = (theme) => {
20
- if (theme === 'dark') {
93
+ /** @deprecated Use {@link getBoardThemeStyles}. */
94
+ const getStylesForTheme = (theme) => getBoardThemeStyles(boardThemeFromLegacyUiTheme());
95
+ const ThemeProvider = ({ children, theme = 'dark', boardTheme, }) => {
96
+ const resolvedBoardTheme = boardTheme !== null && boardTheme !== void 0 ? boardTheme : boardThemeFromLegacyUiTheme();
97
+ const { customDarkSquareStyle, customLightSquareStyle } = getBoardThemeStyles(resolvedBoardTheme);
98
+ return (jsxRuntime.jsx(ChessboardThemeContext.Provider, { value: { customDarkSquareStyle, customLightSquareStyle }, children: children }));
99
+ };
100
+
101
+ var E=r=>{throw TypeError(r)};var P=(r,n,e)=>n.has(r)||E("Cannot "+e);var t=(r,n,e)=>(P(r,n,"read from private field"),e?e.call(r):n.get(r)),s=(r,n,e)=>n.has(r)?E("Cannot add the same private member more than once"):n instanceof WeakSet?n.add(r):n.set(r,e),p=(r,n,e,i)=>(P(r,n,"write to private field"),n.set(r,e),e);var h,B=class{constructor(){s(this,h);this.register=n=>{t(this,h).push(n);};this.unregister=n=>{for(;t(this,h).indexOf(n)!==-1;)t(this,h).splice(t(this,h).indexOf(n),1);};this.backendChanged=n=>{for(let e of t(this,h))e.backendChanged(n);};p(this,h,[]);}};h=new WeakMap;var c$1,l,a,d,k,x,T,D,m,v,f,w=class w{constructor(n,e,i){s(this,c$1);s(this,l);s(this,a);s(this,d);s(this,k);s(this,x,(n,e,i)=>{if(!i.backend)throw new Error(`You must specify a 'backend' property in your Backend entry: ${JSON.stringify(i)}`);let u=i.backend(n,e,i.options),o=i.id,g=!i.id&&u&&u.constructor;if(g&&(o=u.constructor.name),!o)throw new Error(`You must specify an 'id' property in your Backend entry: ${JSON.stringify(i)}
102
+ see this guide: https://github.com/louisbrunner/dnd-multi-backend/tree/master/packages/react-dnd-multi-backend#migrating-from-5xx`);if(g&&console.warn(`Deprecation notice: You are using a pipeline which doesn't include backends' 'id'.
103
+ This might be unsupported in the future, please specify 'id' explicitely for every backend.`),t(this,a)[o])throw new Error(`You must specify a unique 'id' property in your Backend entry:
104
+ ${JSON.stringify(i)} (conflicts with: ${JSON.stringify(t(this,a)[o])})`);return {id:o,instance:u,preview:i.preview??false,transition:i.transition,skipDispatchOnTransition:i.skipDispatchOnTransition??false}});this.setup=()=>{if(!(typeof window>"u")){if(w.isSetUp)throw new Error("Cannot have two MultiBackends at the same time.");w.isSetUp=true,t(this,T).call(this,window),t(this,a)[t(this,c$1)].instance.setup();}};this.teardown=()=>{typeof window>"u"||(w.isSetUp=false,t(this,D).call(this,window),t(this,a)[t(this,c$1)].instance.teardown());};this.connectDragSource=(n,e,i)=>t(this,f).call(this,"connectDragSource",n,e,i);this.connectDragPreview=(n,e,i)=>t(this,f).call(this,"connectDragPreview",n,e,i);this.connectDropTarget=(n,e,i)=>t(this,f).call(this,"connectDropTarget",n,e,i);this.profile=()=>t(this,a)[t(this,c$1)].instance.profile();this.previewEnabled=()=>t(this,a)[t(this,c$1)].preview;this.previewsList=()=>t(this,l);this.backendsList=()=>t(this,d);s(this,T,n=>{for(let e of t(this,d))e.transition&&n.addEventListener(e.transition.event,t(this,m));});s(this,D,n=>{for(let e of t(this,d))e.transition&&n.removeEventListener(e.transition.event,t(this,m));});s(this,m,n=>{let e=t(this,c$1);if(t(this,d).some(i=>i.id!==t(this,c$1)&&i.transition&&i.transition.check(n)?(p(this,c$1,i.id),true):false),t(this,c$1)!==e){t(this,a)[e].instance.teardown();for(let[g,b]of Object.entries(t(this,k)))b.unsubscribe(),b.unsubscribe=t(this,v).call(this,b.func,...b.args);t(this,l).backendChanged(this);let i=t(this,a)[t(this,c$1)];if(i.instance.setup(),i.skipDispatchOnTransition)return;let u=n.constructor,o=new u(n.type,n);n.target?.dispatchEvent(o);}});s(this,v,(n,e,i,u)=>t(this,a)[t(this,c$1)].instance[n](e,i,u));s(this,f,(n,e,i,u)=>{let o=`${n}_${e}`,g=t(this,v).call(this,n,e,i,u);return t(this,k)[o]={func:n,args:[e,i,u],unsubscribe:g},()=>{t(this,k)[o].unsubscribe(),delete t(this,k)[o];}});if(!i||!i.backends||i.backends.length<1)throw new Error(`You must specify at least one Backend, if you are coming from 2.x.x (or don't understand this error)
105
+ see this guide: https://github.com/louisbrunner/dnd-multi-backend/tree/master/packages/react-dnd-multi-backend#migrating-from-2xx`);p(this,l,new B),p(this,a,{}),p(this,d,[]);for(let u of i.backends){let o=t(this,x).call(this,n,e,u);t(this,a)[o.id]=o,t(this,d).push(o);}p(this,c$1,t(this,d)[0].id),p(this,k,{});}};c$1=new WeakMap,l=new WeakMap,a=new WeakMap,d=new WeakMap,k=new WeakMap,x=new WeakMap,T=new WeakMap,D=new WeakMap,m=new WeakMap,v=new WeakMap,f=new WeakMap,w.isSetUp=false;var M=w;var S=(r,n,e)=>new M(r,n,e);var y=(r,n)=>({event:r,check:n});var L=y("touchstart",r=>{let n=r;return n.touches!==null&&n.touches!==void 0}),U=y("pointerdown",r=>r.pointerType==="mouse");
106
+
107
+ /**
108
+ * Use invariant() to assert state which your program assumes to be true.
109
+ *
110
+ * Provide sprintf-style format (only %s is supported) and arguments
111
+ * to provide information about what broke and what you were
112
+ * expecting.
113
+ *
114
+ * The invariant message will be stripped in production, but the invariant
115
+ * will remain to ensure logic does not differ in production.
116
+ */ function invariant(condition, format, ...args) {
117
+ if (isProduction()) ;
118
+ if (!condition) {
119
+ let error;
120
+ {
121
+ let argIndex = 0;
122
+ error = new Error(format.replace(/%s/g, function() {
123
+ return args[argIndex++];
124
+ }));
125
+ error.name = 'Invariant Violation';
126
+ }
127
+ error.framesToPop = 1 // we don't care about invariant's own frame
128
+ ;
129
+ throw error;
130
+ }
131
+ }
132
+ function isProduction() {
133
+ return typeof process !== 'undefined' && process.env['NODE_ENV'] === 'production';
134
+ }
135
+
136
+ // cheap lodash replacements
137
+ function memoize(fn) {
138
+ let result = null;
139
+ const memoized = ()=>{
140
+ if (result == null) {
141
+ result = fn();
142
+ }
143
+ return result;
144
+ };
145
+ return memoized;
146
+ }
147
+ /**
148
+ * drop-in replacement for _.without
149
+ */ function without(items, item) {
150
+ return items.filter((i)=>i !== item
151
+ );
152
+ }
153
+ function union(itemsA, itemsB) {
154
+ const set = new Set();
155
+ const insertItem = (item)=>set.add(item)
156
+ ;
157
+ itemsA.forEach(insertItem);
158
+ itemsB.forEach(insertItem);
159
+ const result = [];
160
+ set.forEach((key)=>result.push(key)
161
+ );
162
+ return result;
163
+ }
164
+
165
+ class EnterLeaveCounter {
166
+ enter(enteringNode) {
167
+ const previousLength = this.entered.length;
168
+ const isNodeEntered = (node)=>this.isNodeInDocument(node) && (!node.contains || node.contains(enteringNode))
169
+ ;
170
+ this.entered = union(this.entered.filter(isNodeEntered), [
171
+ enteringNode
172
+ ]);
173
+ return previousLength === 0 && this.entered.length > 0;
174
+ }
175
+ leave(leavingNode) {
176
+ const previousLength = this.entered.length;
177
+ this.entered = without(this.entered.filter(this.isNodeInDocument), leavingNode);
178
+ return previousLength > 0 && this.entered.length === 0;
179
+ }
180
+ reset() {
181
+ this.entered = [];
182
+ }
183
+ constructor(isNodeInDocument){
184
+ this.entered = [];
185
+ this.isNodeInDocument = isNodeInDocument;
186
+ }
187
+ }
188
+
189
+ class NativeDragSource {
190
+ initializeExposedProperties() {
191
+ Object.keys(this.config.exposeProperties).forEach((property)=>{
192
+ Object.defineProperty(this.item, property, {
193
+ configurable: true,
194
+ enumerable: true,
195
+ get () {
196
+ // eslint-disable-next-line no-console
197
+ console.warn(`Browser doesn't allow reading "${property}" until the drop event.`);
198
+ return null;
199
+ }
200
+ });
201
+ });
202
+ }
203
+ loadDataTransfer(dataTransfer) {
204
+ if (dataTransfer) {
205
+ const newProperties = {};
206
+ Object.keys(this.config.exposeProperties).forEach((property)=>{
207
+ const propertyFn = this.config.exposeProperties[property];
208
+ if (propertyFn != null) {
209
+ newProperties[property] = {
210
+ value: propertyFn(dataTransfer, this.config.matchesTypes),
211
+ configurable: true,
212
+ enumerable: true
213
+ };
214
+ }
215
+ });
216
+ Object.defineProperties(this.item, newProperties);
217
+ }
218
+ }
219
+ canDrag() {
220
+ return true;
221
+ }
222
+ beginDrag() {
223
+ return this.item;
224
+ }
225
+ isDragging(monitor, handle) {
226
+ return handle === monitor.getSourceId();
227
+ }
228
+ endDrag() {
229
+ // empty
230
+ }
231
+ constructor(config){
232
+ this.config = config;
233
+ this.item = {};
234
+ this.initializeExposedProperties();
235
+ }
236
+ }
237
+
238
+ const FILE = '__NATIVE_FILE__';
239
+ const URL$1 = '__NATIVE_URL__';
240
+ const TEXT = '__NATIVE_TEXT__';
241
+ const HTML = '__NATIVE_HTML__';
242
+
243
+ var NativeTypes = /*#__PURE__*/Object.freeze({
244
+ __proto__: null,
245
+ FILE: FILE,
246
+ HTML: HTML,
247
+ TEXT: TEXT,
248
+ URL: URL$1
249
+ });
250
+
251
+ function getDataFromDataTransfer(dataTransfer, typesToTry, defaultValue) {
252
+ const result = typesToTry.reduce((resultSoFar, typeToTry)=>resultSoFar || dataTransfer.getData(typeToTry)
253
+ , '');
254
+ return result != null ? result : defaultValue;
255
+ }
256
+
257
+ const nativeTypesConfig = {
258
+ [FILE]: {
259
+ exposeProperties: {
260
+ files: (dataTransfer)=>Array.prototype.slice.call(dataTransfer.files)
261
+ ,
262
+ items: (dataTransfer)=>dataTransfer.items
263
+ ,
264
+ dataTransfer: (dataTransfer)=>dataTransfer
265
+ },
266
+ matchesTypes: [
267
+ 'Files'
268
+ ]
269
+ },
270
+ [HTML]: {
271
+ exposeProperties: {
272
+ html: (dataTransfer, matchesTypes)=>getDataFromDataTransfer(dataTransfer, matchesTypes, '')
273
+ ,
274
+ dataTransfer: (dataTransfer)=>dataTransfer
275
+ },
276
+ matchesTypes: [
277
+ 'Html',
278
+ 'text/html'
279
+ ]
280
+ },
281
+ [URL$1]: {
282
+ exposeProperties: {
283
+ urls: (dataTransfer, matchesTypes)=>getDataFromDataTransfer(dataTransfer, matchesTypes, '').split('\n')
284
+ ,
285
+ dataTransfer: (dataTransfer)=>dataTransfer
286
+ },
287
+ matchesTypes: [
288
+ 'Url',
289
+ 'text/uri-list'
290
+ ]
291
+ },
292
+ [TEXT]: {
293
+ exposeProperties: {
294
+ text: (dataTransfer, matchesTypes)=>getDataFromDataTransfer(dataTransfer, matchesTypes, '')
295
+ ,
296
+ dataTransfer: (dataTransfer)=>dataTransfer
297
+ },
298
+ matchesTypes: [
299
+ 'Text',
300
+ 'text/plain'
301
+ ]
302
+ }
303
+ };
304
+
305
+ function createNativeDragSource(type, dataTransfer) {
306
+ const config = nativeTypesConfig[type];
307
+ if (!config) {
308
+ throw new Error(`native type ${type} has no configuration`);
309
+ }
310
+ const result = new NativeDragSource(config);
311
+ result.loadDataTransfer(dataTransfer);
312
+ return result;
313
+ }
314
+ function matchNativeItemType(dataTransfer) {
315
+ if (!dataTransfer) {
316
+ return null;
317
+ }
318
+ const dataTransferTypes = Array.prototype.slice.call(dataTransfer.types || []);
319
+ return Object.keys(nativeTypesConfig).filter((nativeItemType)=>{
320
+ const typeConfig = nativeTypesConfig[nativeItemType];
321
+ if (!(typeConfig === null || typeConfig === void 0 ? void 0 : typeConfig.matchesTypes)) {
322
+ return false;
323
+ }
324
+ return typeConfig.matchesTypes.some((t)=>dataTransferTypes.indexOf(t) > -1
325
+ );
326
+ })[0] || null;
327
+ }
328
+
329
+ const isFirefox = memoize(()=>/firefox/i.test(navigator.userAgent)
330
+ );
331
+ const isSafari = memoize(()=>Boolean(window.safari)
332
+ );
333
+
334
+ class MonotonicInterpolant {
335
+ interpolate(x) {
336
+ const { xs , ys , c1s , c2s , c3s } = this;
337
+ // The rightmost point in the dataset should give an exact result
338
+ let i = xs.length - 1;
339
+ if (x === xs[i]) {
340
+ return ys[i];
341
+ }
342
+ // Search for the interval x is in, returning the corresponding y if x is one of the original xs
343
+ let low = 0;
344
+ let high = c3s.length - 1;
345
+ let mid;
346
+ while(low <= high){
347
+ mid = Math.floor(0.5 * (low + high));
348
+ const xHere = xs[mid];
349
+ if (xHere < x) {
350
+ low = mid + 1;
351
+ } else if (xHere > x) {
352
+ high = mid - 1;
353
+ } else {
354
+ return ys[mid];
355
+ }
356
+ }
357
+ i = Math.max(0, high);
358
+ // Interpolate
359
+ const diff = x - xs[i];
360
+ const diffSq = diff * diff;
361
+ return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;
362
+ }
363
+ constructor(xs, ys){
364
+ const { length } = xs;
365
+ // Rearrange xs and ys so that xs is sorted
366
+ const indexes = [];
367
+ for(let i = 0; i < length; i++){
368
+ indexes.push(i);
369
+ }
370
+ indexes.sort((a, b)=>xs[a] < xs[b] ? -1 : 1
371
+ );
372
+ const dxs = [];
373
+ const ms = [];
374
+ let dx;
375
+ let dy;
376
+ for(let i1 = 0; i1 < length - 1; i1++){
377
+ dx = xs[i1 + 1] - xs[i1];
378
+ dy = ys[i1 + 1] - ys[i1];
379
+ dxs.push(dx);
380
+ ms.push(dy / dx);
381
+ }
382
+ // Get degree-1 coefficients
383
+ const c1s = [
384
+ ms[0]
385
+ ];
386
+ for(let i2 = 0; i2 < dxs.length - 1; i2++){
387
+ const m2 = ms[i2];
388
+ const mNext = ms[i2 + 1];
389
+ if (m2 * mNext <= 0) {
390
+ c1s.push(0);
391
+ } else {
392
+ dx = dxs[i2];
393
+ const dxNext = dxs[i2 + 1];
394
+ const common = dx + dxNext;
395
+ c1s.push(3 * common / ((common + dxNext) / m2 + (common + dx) / mNext));
396
+ }
397
+ }
398
+ c1s.push(ms[ms.length - 1]);
399
+ // Get degree-2 and degree-3 coefficients
400
+ const c2s = [];
401
+ const c3s = [];
402
+ let m;
403
+ for(let i3 = 0; i3 < c1s.length - 1; i3++){
404
+ m = ms[i3];
405
+ const c1 = c1s[i3];
406
+ const invDx = 1 / dxs[i3];
407
+ const common = c1 + c1s[i3 + 1] - m - m;
408
+ c2s.push((m - c1 - common) * invDx);
409
+ c3s.push(common * invDx * invDx);
410
+ }
411
+ this.xs = xs;
412
+ this.ys = ys;
413
+ this.c1s = c1s;
414
+ this.c2s = c2s;
415
+ this.c3s = c3s;
416
+ }
417
+ }
418
+
419
+ const ELEMENT_NODE$1 = 1;
420
+ function getNodeClientOffset$1(node) {
421
+ const el = node.nodeType === ELEMENT_NODE$1 ? node : node.parentElement;
422
+ if (!el) {
423
+ return null;
424
+ }
425
+ const { top , left } = el.getBoundingClientRect();
426
+ return {
427
+ x: left,
428
+ y: top
429
+ };
430
+ }
431
+ function getEventClientOffset$1(e) {
432
+ return {
433
+ x: e.clientX,
434
+ y: e.clientY
435
+ };
436
+ }
437
+ function isImageNode(node) {
438
+ var ref;
439
+ return node.nodeName === 'IMG' && (isFirefox() || !((ref = document.documentElement) === null || ref === void 0 ? void 0 : ref.contains(node)));
440
+ }
441
+ function getDragPreviewSize(isImage, dragPreview, sourceWidth, sourceHeight) {
442
+ let dragPreviewWidth = isImage ? dragPreview.width : sourceWidth;
443
+ let dragPreviewHeight = isImage ? dragPreview.height : sourceHeight;
444
+ // Work around @2x coordinate discrepancies in browsers
445
+ if (isSafari() && isImage) {
446
+ dragPreviewHeight /= window.devicePixelRatio;
447
+ dragPreviewWidth /= window.devicePixelRatio;
448
+ }
449
+ return {
450
+ dragPreviewWidth,
451
+ dragPreviewHeight
452
+ };
453
+ }
454
+ function getDragPreviewOffset(sourceNode, dragPreview, clientOffset, anchorPoint, offsetPoint) {
455
+ // The browsers will use the image intrinsic size under different conditions.
456
+ // Firefox only cares if it's an image, but WebKit also wants it to be detached.
457
+ const isImage = isImageNode(dragPreview);
458
+ const dragPreviewNode = isImage ? sourceNode : dragPreview;
459
+ const dragPreviewNodeOffsetFromClient = getNodeClientOffset$1(dragPreviewNode);
460
+ const offsetFromDragPreview = {
461
+ x: clientOffset.x - dragPreviewNodeOffsetFromClient.x,
462
+ y: clientOffset.y - dragPreviewNodeOffsetFromClient.y
463
+ };
464
+ const { offsetWidth: sourceWidth , offsetHeight: sourceHeight } = sourceNode;
465
+ const { anchorX , anchorY } = anchorPoint;
466
+ const { dragPreviewWidth , dragPreviewHeight } = getDragPreviewSize(isImage, dragPreview, sourceWidth, sourceHeight);
467
+ const calculateYOffset = ()=>{
468
+ const interpolantY = new MonotonicInterpolant([
469
+ 0,
470
+ 0.5,
471
+ 1
472
+ ], [
473
+ // Dock to the top
474
+ offsetFromDragPreview.y,
475
+ // Align at the center
476
+ (offsetFromDragPreview.y / sourceHeight) * dragPreviewHeight,
477
+ // Dock to the bottom
478
+ offsetFromDragPreview.y + dragPreviewHeight - sourceHeight,
479
+ ]);
480
+ let y = interpolantY.interpolate(anchorY);
481
+ // Work around Safari 8 positioning bug
482
+ if (isSafari() && isImage) {
483
+ // We'll have to wait for @3x to see if this is entirely correct
484
+ y += (window.devicePixelRatio - 1) * dragPreviewHeight;
485
+ }
486
+ return y;
487
+ };
488
+ const calculateXOffset = ()=>{
489
+ // Interpolate coordinates depending on anchor point
490
+ // If you know a simpler way to do this, let me know
491
+ const interpolantX = new MonotonicInterpolant([
492
+ 0,
493
+ 0.5,
494
+ 1
495
+ ], [
496
+ // Dock to the left
497
+ offsetFromDragPreview.x,
498
+ // Align at the center
499
+ (offsetFromDragPreview.x / sourceWidth) * dragPreviewWidth,
500
+ // Dock to the right
501
+ offsetFromDragPreview.x + dragPreviewWidth - sourceWidth,
502
+ ]);
503
+ return interpolantX.interpolate(anchorX);
504
+ };
505
+ // Force offsets if specified in the options.
506
+ const { offsetX , offsetY } = offsetPoint;
507
+ const isManualOffsetX = offsetX === 0 || offsetX;
508
+ const isManualOffsetY = offsetY === 0 || offsetY;
509
+ return {
510
+ x: isManualOffsetX ? offsetX : calculateXOffset(),
511
+ y: isManualOffsetY ? offsetY : calculateYOffset()
512
+ };
513
+ }
514
+
515
+ let OptionsReader$1 = class OptionsReader {
516
+ get window() {
517
+ if (this.globalContext) {
518
+ return this.globalContext;
519
+ } else if (typeof window !== 'undefined') {
520
+ return window;
521
+ }
522
+ return undefined;
523
+ }
524
+ get document() {
525
+ var ref;
526
+ if ((ref = this.globalContext) === null || ref === void 0 ? void 0 : ref.document) {
527
+ return this.globalContext.document;
528
+ } else if (this.window) {
529
+ return this.window.document;
530
+ } else {
531
+ return undefined;
532
+ }
533
+ }
534
+ get rootElement() {
535
+ var ref;
536
+ return ((ref = this.optionsArgs) === null || ref === void 0 ? void 0 : ref.rootElement) || this.window;
537
+ }
538
+ constructor(globalContext, options){
539
+ this.ownerDocument = null;
540
+ this.globalContext = globalContext;
541
+ this.optionsArgs = options;
542
+ }
543
+ };
544
+
545
+ function _defineProperty(obj, key, value) {
546
+ if (key in obj) {
547
+ Object.defineProperty(obj, key, {
548
+ value: value,
549
+ enumerable: true,
550
+ configurable: true,
551
+ writable: true
552
+ });
553
+ } else {
554
+ obj[key] = value;
555
+ }
556
+ return obj;
557
+ }
558
+ function _objectSpread(target) {
559
+ for(var i = 1; i < arguments.length; i++){
560
+ var source = arguments[i] != null ? arguments[i] : {};
561
+ var ownKeys = Object.keys(source);
562
+ if (typeof Object.getOwnPropertySymbols === 'function') {
563
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
564
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
565
+ }));
566
+ }
567
+ ownKeys.forEach(function(key) {
568
+ _defineProperty(target, key, source[key]);
569
+ });
570
+ }
571
+ return target;
572
+ }
573
+ class HTML5BackendImpl {
574
+ /**
575
+ * Generate profiling statistics for the HTML5Backend.
576
+ */ profile() {
577
+ var ref, ref1;
21
578
  return {
22
- customDarkSquareStyle: { backgroundColor: '#838387' },
23
- customLightSquareStyle: { backgroundColor: '#e1e1e3' },
579
+ sourcePreviewNodes: this.sourcePreviewNodes.size,
580
+ sourcePreviewNodeOptions: this.sourcePreviewNodeOptions.size,
581
+ sourceNodeOptions: this.sourceNodeOptions.size,
582
+ sourceNodes: this.sourceNodes.size,
583
+ dragStartSourceIds: ((ref = this.dragStartSourceIds) === null || ref === void 0 ? void 0 : ref.length) || 0,
584
+ dropTargetIds: this.dropTargetIds.length,
585
+ dragEnterTargetIds: this.dragEnterTargetIds.length,
586
+ dragOverTargetIds: ((ref1 = this.dragOverTargetIds) === null || ref1 === void 0 ? void 0 : ref1.length) || 0
587
+ };
588
+ }
589
+ // public for test
590
+ get window() {
591
+ return this.options.window;
592
+ }
593
+ get document() {
594
+ return this.options.document;
595
+ }
596
+ /**
597
+ * Get the root element to use for event subscriptions
598
+ */ get rootElement() {
599
+ return this.options.rootElement;
600
+ }
601
+ setup() {
602
+ const root = this.rootElement;
603
+ if (root === undefined) {
604
+ return;
605
+ }
606
+ if (root.__isReactDndBackendSetUp) {
607
+ throw new Error('Cannot have two HTML5 backends at the same time.');
608
+ }
609
+ root.__isReactDndBackendSetUp = true;
610
+ this.addEventListeners(root);
611
+ }
612
+ teardown() {
613
+ const root = this.rootElement;
614
+ if (root === undefined) {
615
+ return;
616
+ }
617
+ root.__isReactDndBackendSetUp = false;
618
+ this.removeEventListeners(this.rootElement);
619
+ this.clearCurrentDragSourceNode();
620
+ if (this.asyncEndDragFrameId) {
621
+ var ref;
622
+ (ref = this.window) === null || ref === void 0 ? void 0 : ref.cancelAnimationFrame(this.asyncEndDragFrameId);
623
+ }
624
+ }
625
+ connectDragPreview(sourceId, node, options) {
626
+ this.sourcePreviewNodeOptions.set(sourceId, options);
627
+ this.sourcePreviewNodes.set(sourceId, node);
628
+ return ()=>{
629
+ this.sourcePreviewNodes.delete(sourceId);
630
+ this.sourcePreviewNodeOptions.delete(sourceId);
631
+ };
632
+ }
633
+ connectDragSource(sourceId, node, options) {
634
+ this.sourceNodes.set(sourceId, node);
635
+ this.sourceNodeOptions.set(sourceId, options);
636
+ const handleDragStart = (e)=>this.handleDragStart(e, sourceId)
637
+ ;
638
+ const handleSelectStart = (e)=>this.handleSelectStart(e)
639
+ ;
640
+ node.setAttribute('draggable', 'true');
641
+ node.addEventListener('dragstart', handleDragStart);
642
+ node.addEventListener('selectstart', handleSelectStart);
643
+ return ()=>{
644
+ this.sourceNodes.delete(sourceId);
645
+ this.sourceNodeOptions.delete(sourceId);
646
+ node.removeEventListener('dragstart', handleDragStart);
647
+ node.removeEventListener('selectstart', handleSelectStart);
648
+ node.setAttribute('draggable', 'false');
649
+ };
650
+ }
651
+ connectDropTarget(targetId, node) {
652
+ const handleDragEnter = (e)=>this.handleDragEnter(e, targetId)
653
+ ;
654
+ const handleDragOver = (e)=>this.handleDragOver(e, targetId)
655
+ ;
656
+ const handleDrop = (e)=>this.handleDrop(e, targetId)
657
+ ;
658
+ node.addEventListener('dragenter', handleDragEnter);
659
+ node.addEventListener('dragover', handleDragOver);
660
+ node.addEventListener('drop', handleDrop);
661
+ return ()=>{
662
+ node.removeEventListener('dragenter', handleDragEnter);
663
+ node.removeEventListener('dragover', handleDragOver);
664
+ node.removeEventListener('drop', handleDrop);
665
+ };
666
+ }
667
+ addEventListeners(target) {
668
+ // SSR Fix (https://github.com/react-dnd/react-dnd/pull/813
669
+ if (!target.addEventListener) {
670
+ return;
671
+ }
672
+ target.addEventListener('dragstart', this.handleTopDragStart);
673
+ target.addEventListener('dragstart', this.handleTopDragStartCapture, true);
674
+ target.addEventListener('dragend', this.handleTopDragEndCapture, true);
675
+ target.addEventListener('dragenter', this.handleTopDragEnter);
676
+ target.addEventListener('dragenter', this.handleTopDragEnterCapture, true);
677
+ target.addEventListener('dragleave', this.handleTopDragLeaveCapture, true);
678
+ target.addEventListener('dragover', this.handleTopDragOver);
679
+ target.addEventListener('dragover', this.handleTopDragOverCapture, true);
680
+ target.addEventListener('drop', this.handleTopDrop);
681
+ target.addEventListener('drop', this.handleTopDropCapture, true);
682
+ }
683
+ removeEventListeners(target) {
684
+ // SSR Fix (https://github.com/react-dnd/react-dnd/pull/813
685
+ if (!target.removeEventListener) {
686
+ return;
687
+ }
688
+ target.removeEventListener('dragstart', this.handleTopDragStart);
689
+ target.removeEventListener('dragstart', this.handleTopDragStartCapture, true);
690
+ target.removeEventListener('dragend', this.handleTopDragEndCapture, true);
691
+ target.removeEventListener('dragenter', this.handleTopDragEnter);
692
+ target.removeEventListener('dragenter', this.handleTopDragEnterCapture, true);
693
+ target.removeEventListener('dragleave', this.handleTopDragLeaveCapture, true);
694
+ target.removeEventListener('dragover', this.handleTopDragOver);
695
+ target.removeEventListener('dragover', this.handleTopDragOverCapture, true);
696
+ target.removeEventListener('drop', this.handleTopDrop);
697
+ target.removeEventListener('drop', this.handleTopDropCapture, true);
698
+ }
699
+ getCurrentSourceNodeOptions() {
700
+ const sourceId = this.monitor.getSourceId();
701
+ const sourceNodeOptions = this.sourceNodeOptions.get(sourceId);
702
+ return _objectSpread({
703
+ dropEffect: this.altKeyPressed ? 'copy' : 'move'
704
+ }, sourceNodeOptions || {});
705
+ }
706
+ getCurrentDropEffect() {
707
+ if (this.isDraggingNativeItem()) {
708
+ // It makes more sense to default to 'copy' for native resources
709
+ return 'copy';
710
+ }
711
+ return this.getCurrentSourceNodeOptions().dropEffect;
712
+ }
713
+ getCurrentSourcePreviewNodeOptions() {
714
+ const sourceId = this.monitor.getSourceId();
715
+ const sourcePreviewNodeOptions = this.sourcePreviewNodeOptions.get(sourceId);
716
+ return _objectSpread({
717
+ anchorX: 0.5,
718
+ anchorY: 0.5,
719
+ captureDraggingState: false
720
+ }, sourcePreviewNodeOptions || {});
721
+ }
722
+ isDraggingNativeItem() {
723
+ const itemType = this.monitor.getItemType();
724
+ return Object.keys(NativeTypes).some((key)=>NativeTypes[key] === itemType
725
+ );
726
+ }
727
+ beginDragNativeItem(type, dataTransfer) {
728
+ this.clearCurrentDragSourceNode();
729
+ this.currentNativeSource = createNativeDragSource(type, dataTransfer);
730
+ this.currentNativeHandle = this.registry.addSource(type, this.currentNativeSource);
731
+ this.actions.beginDrag([
732
+ this.currentNativeHandle
733
+ ]);
734
+ }
735
+ setCurrentDragSourceNode(node) {
736
+ this.clearCurrentDragSourceNode();
737
+ this.currentDragSourceNode = node;
738
+ // A timeout of > 0 is necessary to resolve Firefox issue referenced
739
+ // See:
740
+ // * https://github.com/react-dnd/react-dnd/pull/928
741
+ // * https://github.com/react-dnd/react-dnd/issues/869
742
+ const MOUSE_MOVE_TIMEOUT = 1000;
743
+ // Receiving a mouse event in the middle of a dragging operation
744
+ // means it has ended and the drag source node disappeared from DOM,
745
+ // so the browser didn't dispatch the dragend event.
746
+ //
747
+ // We need to wait before we start listening for mousemove events.
748
+ // This is needed because the drag preview needs to be drawn or else it fires an 'mousemove' event
749
+ // immediately in some browsers.
750
+ //
751
+ // See:
752
+ // * https://github.com/react-dnd/react-dnd/pull/928
753
+ // * https://github.com/react-dnd/react-dnd/issues/869
754
+ //
755
+ this.mouseMoveTimeoutTimer = setTimeout(()=>{
756
+ var ref;
757
+ return (ref = this.rootElement) === null || ref === void 0 ? void 0 : ref.addEventListener('mousemove', this.endDragIfSourceWasRemovedFromDOM, true);
758
+ }, MOUSE_MOVE_TIMEOUT);
759
+ }
760
+ clearCurrentDragSourceNode() {
761
+ if (this.currentDragSourceNode) {
762
+ this.currentDragSourceNode = null;
763
+ if (this.rootElement) {
764
+ var ref;
765
+ (ref = this.window) === null || ref === void 0 ? void 0 : ref.clearTimeout(this.mouseMoveTimeoutTimer || undefined);
766
+ this.rootElement.removeEventListener('mousemove', this.endDragIfSourceWasRemovedFromDOM, true);
767
+ }
768
+ this.mouseMoveTimeoutTimer = null;
769
+ return true;
770
+ }
771
+ return false;
772
+ }
773
+ handleDragStart(e, sourceId) {
774
+ if (e.defaultPrevented) {
775
+ return;
776
+ }
777
+ if (!this.dragStartSourceIds) {
778
+ this.dragStartSourceIds = [];
779
+ }
780
+ this.dragStartSourceIds.unshift(sourceId);
781
+ }
782
+ handleDragEnter(_e, targetId) {
783
+ this.dragEnterTargetIds.unshift(targetId);
784
+ }
785
+ handleDragOver(_e, targetId) {
786
+ if (this.dragOverTargetIds === null) {
787
+ this.dragOverTargetIds = [];
788
+ }
789
+ this.dragOverTargetIds.unshift(targetId);
790
+ }
791
+ handleDrop(_e, targetId) {
792
+ this.dropTargetIds.unshift(targetId);
793
+ }
794
+ constructor(manager, globalContext, options){
795
+ this.sourcePreviewNodes = new Map();
796
+ this.sourcePreviewNodeOptions = new Map();
797
+ this.sourceNodes = new Map();
798
+ this.sourceNodeOptions = new Map();
799
+ this.dragStartSourceIds = null;
800
+ this.dropTargetIds = [];
801
+ this.dragEnterTargetIds = [];
802
+ this.currentNativeSource = null;
803
+ this.currentNativeHandle = null;
804
+ this.currentDragSourceNode = null;
805
+ this.altKeyPressed = false;
806
+ this.mouseMoveTimeoutTimer = null;
807
+ this.asyncEndDragFrameId = null;
808
+ this.dragOverTargetIds = null;
809
+ this.lastClientOffset = null;
810
+ this.hoverRafId = null;
811
+ this.getSourceClientOffset = (sourceId)=>{
812
+ const source = this.sourceNodes.get(sourceId);
813
+ return source && getNodeClientOffset$1(source) || null;
814
+ };
815
+ this.endDragNativeItem = ()=>{
816
+ if (!this.isDraggingNativeItem()) {
817
+ return;
818
+ }
819
+ this.actions.endDrag();
820
+ if (this.currentNativeHandle) {
821
+ this.registry.removeSource(this.currentNativeHandle);
822
+ }
823
+ this.currentNativeHandle = null;
824
+ this.currentNativeSource = null;
825
+ };
826
+ this.isNodeInDocument = (node)=>{
827
+ // Check the node either in the main document or in the current context
828
+ return Boolean(node && this.document && this.document.body && this.document.body.contains(node));
829
+ };
830
+ this.endDragIfSourceWasRemovedFromDOM = ()=>{
831
+ const node = this.currentDragSourceNode;
832
+ if (node == null || this.isNodeInDocument(node)) {
833
+ return;
834
+ }
835
+ if (this.clearCurrentDragSourceNode() && this.monitor.isDragging()) {
836
+ this.actions.endDrag();
837
+ }
838
+ this.cancelHover();
839
+ };
840
+ this.scheduleHover = (dragOverTargetIds)=>{
841
+ if (this.hoverRafId === null && typeof requestAnimationFrame !== 'undefined') {
842
+ this.hoverRafId = requestAnimationFrame(()=>{
843
+ if (this.monitor.isDragging()) {
844
+ this.actions.hover(dragOverTargetIds || [], {
845
+ clientOffset: this.lastClientOffset
846
+ });
847
+ }
848
+ this.hoverRafId = null;
849
+ });
850
+ }
851
+ };
852
+ this.cancelHover = ()=>{
853
+ if (this.hoverRafId !== null && typeof cancelAnimationFrame !== 'undefined') {
854
+ cancelAnimationFrame(this.hoverRafId);
855
+ this.hoverRafId = null;
856
+ }
857
+ };
858
+ this.handleTopDragStartCapture = ()=>{
859
+ this.clearCurrentDragSourceNode();
860
+ this.dragStartSourceIds = [];
861
+ };
862
+ this.handleTopDragStart = (e)=>{
863
+ if (e.defaultPrevented) {
864
+ return;
865
+ }
866
+ const { dragStartSourceIds } = this;
867
+ this.dragStartSourceIds = null;
868
+ const clientOffset = getEventClientOffset$1(e);
869
+ // Avoid crashing if we missed a drop event or our previous drag died
870
+ if (this.monitor.isDragging()) {
871
+ this.actions.endDrag();
872
+ this.cancelHover();
873
+ }
874
+ // Don't publish the source just yet (see why below)
875
+ this.actions.beginDrag(dragStartSourceIds || [], {
876
+ publishSource: false,
877
+ getSourceClientOffset: this.getSourceClientOffset,
878
+ clientOffset
879
+ });
880
+ const { dataTransfer } = e;
881
+ const nativeType = matchNativeItemType(dataTransfer);
882
+ if (this.monitor.isDragging()) {
883
+ if (dataTransfer && typeof dataTransfer.setDragImage === 'function') {
884
+ // Use custom drag image if user specifies it.
885
+ // If child drag source refuses drag but parent agrees,
886
+ // use parent's node as drag image. Neither works in IE though.
887
+ const sourceId = this.monitor.getSourceId();
888
+ const sourceNode = this.sourceNodes.get(sourceId);
889
+ const dragPreview = this.sourcePreviewNodes.get(sourceId) || sourceNode;
890
+ if (dragPreview) {
891
+ const { anchorX , anchorY , offsetX , offsetY } = this.getCurrentSourcePreviewNodeOptions();
892
+ const anchorPoint = {
893
+ anchorX,
894
+ anchorY
895
+ };
896
+ const offsetPoint = {
897
+ offsetX,
898
+ offsetY
899
+ };
900
+ const dragPreviewOffset = getDragPreviewOffset(sourceNode, dragPreview, clientOffset, anchorPoint, offsetPoint);
901
+ dataTransfer.setDragImage(dragPreview, dragPreviewOffset.x, dragPreviewOffset.y);
902
+ }
903
+ }
904
+ try {
905
+ // Firefox won't drag without setting data
906
+ dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.setData('application/json', {});
907
+ } catch (err) {
908
+ // IE doesn't support MIME types in setData
909
+ }
910
+ // Store drag source node so we can check whether
911
+ // it is removed from DOM and trigger endDrag manually.
912
+ this.setCurrentDragSourceNode(e.target);
913
+ // Now we are ready to publish the drag source.. or are we not?
914
+ const { captureDraggingState } = this.getCurrentSourcePreviewNodeOptions();
915
+ if (!captureDraggingState) {
916
+ // Usually we want to publish it in the next tick so that browser
917
+ // is able to screenshot the current (not yet dragging) state.
918
+ //
919
+ // It also neatly avoids a situation where render() returns null
920
+ // in the same tick for the source element, and browser freaks out.
921
+ setTimeout(()=>this.actions.publishDragSource()
922
+ , 0);
923
+ } else {
924
+ // In some cases the user may want to override this behavior, e.g.
925
+ // to work around IE not supporting custom drag previews.
926
+ //
927
+ // When using a custom drag layer, the only way to prevent
928
+ // the default drag preview from drawing in IE is to screenshot
929
+ // the dragging state in which the node itself has zero opacity
930
+ // and height. In this case, though, returning null from render()
931
+ // will abruptly end the dragging, which is not obvious.
932
+ //
933
+ // This is the reason such behavior is strictly opt-in.
934
+ this.actions.publishDragSource();
935
+ }
936
+ } else if (nativeType) {
937
+ // A native item (such as URL) dragged from inside the document
938
+ this.beginDragNativeItem(nativeType);
939
+ } else if (dataTransfer && !dataTransfer.types && (e.target && !e.target.hasAttribute || !e.target.hasAttribute('draggable'))) {
940
+ // Looks like a Safari bug: dataTransfer.types is null, but there was no draggable.
941
+ // Just let it drag. It's a native type (URL or text) and will be picked up in
942
+ // dragenter handler.
943
+ return;
944
+ } else {
945
+ // If by this time no drag source reacted, tell browser not to drag.
946
+ e.preventDefault();
947
+ }
948
+ };
949
+ this.handleTopDragEndCapture = ()=>{
950
+ if (this.clearCurrentDragSourceNode() && this.monitor.isDragging()) {
951
+ // Firefox can dispatch this event in an infinite loop
952
+ // if dragend handler does something like showing an alert.
953
+ // Only proceed if we have not handled it already.
954
+ this.actions.endDrag();
955
+ }
956
+ this.cancelHover();
957
+ };
958
+ this.handleTopDragEnterCapture = (e)=>{
959
+ this.dragEnterTargetIds = [];
960
+ if (this.isDraggingNativeItem()) {
961
+ var ref;
962
+ (ref = this.currentNativeSource) === null || ref === void 0 ? void 0 : ref.loadDataTransfer(e.dataTransfer);
963
+ }
964
+ const isFirstEnter = this.enterLeaveCounter.enter(e.target);
965
+ if (!isFirstEnter || this.monitor.isDragging()) {
966
+ return;
967
+ }
968
+ const { dataTransfer } = e;
969
+ const nativeType = matchNativeItemType(dataTransfer);
970
+ if (nativeType) {
971
+ // A native item (such as file or URL) dragged from outside the document
972
+ this.beginDragNativeItem(nativeType, dataTransfer);
973
+ }
974
+ };
975
+ this.handleTopDragEnter = (e)=>{
976
+ const { dragEnterTargetIds } = this;
977
+ this.dragEnterTargetIds = [];
978
+ if (!this.monitor.isDragging()) {
979
+ // This is probably a native item type we don't understand.
980
+ return;
981
+ }
982
+ this.altKeyPressed = e.altKey;
983
+ // If the target changes position as the result of `dragenter`, `dragover` might still
984
+ // get dispatched despite target being no longer there. The easy solution is to check
985
+ // whether there actually is a target before firing `hover`.
986
+ if (dragEnterTargetIds.length > 0) {
987
+ this.actions.hover(dragEnterTargetIds, {
988
+ clientOffset: getEventClientOffset$1(e)
989
+ });
990
+ }
991
+ const canDrop = dragEnterTargetIds.some((targetId)=>this.monitor.canDropOnTarget(targetId)
992
+ );
993
+ if (canDrop) {
994
+ // IE requires this to fire dragover events
995
+ e.preventDefault();
996
+ if (e.dataTransfer) {
997
+ e.dataTransfer.dropEffect = this.getCurrentDropEffect();
998
+ }
999
+ }
1000
+ };
1001
+ this.handleTopDragOverCapture = (e)=>{
1002
+ this.dragOverTargetIds = [];
1003
+ if (this.isDraggingNativeItem()) {
1004
+ var ref;
1005
+ (ref = this.currentNativeSource) === null || ref === void 0 ? void 0 : ref.loadDataTransfer(e.dataTransfer);
1006
+ }
1007
+ };
1008
+ this.handleTopDragOver = (e)=>{
1009
+ const { dragOverTargetIds } = this;
1010
+ this.dragOverTargetIds = [];
1011
+ if (!this.monitor.isDragging()) {
1012
+ // This is probably a native item type we don't understand.
1013
+ // Prevent default "drop and blow away the whole document" action.
1014
+ e.preventDefault();
1015
+ if (e.dataTransfer) {
1016
+ e.dataTransfer.dropEffect = 'none';
1017
+ }
1018
+ return;
1019
+ }
1020
+ this.altKeyPressed = e.altKey;
1021
+ this.lastClientOffset = getEventClientOffset$1(e);
1022
+ this.scheduleHover(dragOverTargetIds);
1023
+ const canDrop = (dragOverTargetIds || []).some((targetId)=>this.monitor.canDropOnTarget(targetId)
1024
+ );
1025
+ if (canDrop) {
1026
+ // Show user-specified drop effect.
1027
+ e.preventDefault();
1028
+ if (e.dataTransfer) {
1029
+ e.dataTransfer.dropEffect = this.getCurrentDropEffect();
1030
+ }
1031
+ } else if (this.isDraggingNativeItem()) {
1032
+ // Don't show a nice cursor but still prevent default
1033
+ // "drop and blow away the whole document" action.
1034
+ e.preventDefault();
1035
+ } else {
1036
+ e.preventDefault();
1037
+ if (e.dataTransfer) {
1038
+ e.dataTransfer.dropEffect = 'none';
1039
+ }
1040
+ }
1041
+ };
1042
+ this.handleTopDragLeaveCapture = (e)=>{
1043
+ if (this.isDraggingNativeItem()) {
1044
+ e.preventDefault();
1045
+ }
1046
+ const isLastLeave = this.enterLeaveCounter.leave(e.target);
1047
+ if (!isLastLeave) {
1048
+ return;
1049
+ }
1050
+ if (this.isDraggingNativeItem()) {
1051
+ setTimeout(()=>this.endDragNativeItem()
1052
+ , 0);
1053
+ }
1054
+ this.cancelHover();
1055
+ };
1056
+ this.handleTopDropCapture = (e)=>{
1057
+ this.dropTargetIds = [];
1058
+ if (this.isDraggingNativeItem()) {
1059
+ var ref;
1060
+ e.preventDefault();
1061
+ (ref = this.currentNativeSource) === null || ref === void 0 ? void 0 : ref.loadDataTransfer(e.dataTransfer);
1062
+ } else if (matchNativeItemType(e.dataTransfer)) {
1063
+ // Dragging some elements, like <a> and <img> may still behave like a native drag event,
1064
+ // even if the current drag event matches a user-defined type.
1065
+ // Stop the default behavior when we're not expecting a native item to be dropped.
1066
+ e.preventDefault();
1067
+ }
1068
+ this.enterLeaveCounter.reset();
24
1069
  };
1070
+ this.handleTopDrop = (e)=>{
1071
+ const { dropTargetIds } = this;
1072
+ this.dropTargetIds = [];
1073
+ this.actions.hover(dropTargetIds, {
1074
+ clientOffset: getEventClientOffset$1(e)
1075
+ });
1076
+ this.actions.drop({
1077
+ dropEffect: this.getCurrentDropEffect()
1078
+ });
1079
+ if (this.isDraggingNativeItem()) {
1080
+ this.endDragNativeItem();
1081
+ } else if (this.monitor.isDragging()) {
1082
+ this.actions.endDrag();
1083
+ }
1084
+ this.cancelHover();
1085
+ };
1086
+ this.handleSelectStart = (e)=>{
1087
+ const target = e.target;
1088
+ // Only IE requires us to explicitly say
1089
+ // we want drag drop operation to start
1090
+ if (typeof target.dragDrop !== 'function') {
1091
+ return;
1092
+ }
1093
+ // Inputs and textareas should be selectable
1094
+ if (target.tagName === 'INPUT' || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
1095
+ return;
1096
+ }
1097
+ // For other targets, ask IE
1098
+ // to enable drag and drop
1099
+ e.preventDefault();
1100
+ target.dragDrop();
1101
+ };
1102
+ this.options = new OptionsReader$1(globalContext, options);
1103
+ this.actions = manager.getActions();
1104
+ this.monitor = manager.getMonitor();
1105
+ this.registry = manager.getRegistry();
1106
+ this.enterLeaveCounter = new EnterLeaveCounter(this.isNodeInDocument);
1107
+ }
1108
+ }
1109
+
1110
+ const HTML5Backend = function createBackend(manager, context, options) {
1111
+ return new HTML5BackendImpl(manager, context, options);
1112
+ };
1113
+
1114
+ var ListenerType;
1115
+ (function(ListenerType) {
1116
+ ListenerType["mouse"] = "mouse";
1117
+ ListenerType["touch"] = "touch";
1118
+ ListenerType["keyboard"] = "keyboard";
1119
+ })(ListenerType || (ListenerType = {}));
1120
+
1121
+ class OptionsReader {
1122
+ get delay() {
1123
+ var _delay;
1124
+ return (_delay = this.args.delay) !== null && _delay !== void 0 ? _delay : 0;
1125
+ }
1126
+ get scrollAngleRanges() {
1127
+ return this.args.scrollAngleRanges;
1128
+ }
1129
+ get getDropTargetElementsAtPoint() {
1130
+ return this.args.getDropTargetElementsAtPoint;
1131
+ }
1132
+ get ignoreContextMenu() {
1133
+ var _ignoreContextMenu;
1134
+ return (_ignoreContextMenu = this.args.ignoreContextMenu) !== null && _ignoreContextMenu !== void 0 ? _ignoreContextMenu : false;
1135
+ }
1136
+ get enableHoverOutsideTarget() {
1137
+ var _enableHoverOutsideTarget;
1138
+ return (_enableHoverOutsideTarget = this.args.enableHoverOutsideTarget) !== null && _enableHoverOutsideTarget !== void 0 ? _enableHoverOutsideTarget : false;
1139
+ }
1140
+ get enableKeyboardEvents() {
1141
+ var _enableKeyboardEvents;
1142
+ return (_enableKeyboardEvents = this.args.enableKeyboardEvents) !== null && _enableKeyboardEvents !== void 0 ? _enableKeyboardEvents : false;
1143
+ }
1144
+ get enableMouseEvents() {
1145
+ var _enableMouseEvents;
1146
+ return (_enableMouseEvents = this.args.enableMouseEvents) !== null && _enableMouseEvents !== void 0 ? _enableMouseEvents : false;
1147
+ }
1148
+ get enableTouchEvents() {
1149
+ var _enableTouchEvents;
1150
+ return (_enableTouchEvents = this.args.enableTouchEvents) !== null && _enableTouchEvents !== void 0 ? _enableTouchEvents : true;
1151
+ }
1152
+ get touchSlop() {
1153
+ return this.args.touchSlop || 0;
1154
+ }
1155
+ get delayTouchStart() {
1156
+ var ref, ref1;
1157
+ var ref2, ref3;
1158
+ return (ref3 = (ref2 = (ref = this.args) === null || ref === void 0 ? void 0 : ref.delayTouchStart) !== null && ref2 !== void 0 ? ref2 : (ref1 = this.args) === null || ref1 === void 0 ? void 0 : ref1.delay) !== null && ref3 !== void 0 ? ref3 : 0;
1159
+ }
1160
+ get delayMouseStart() {
1161
+ var ref, ref4;
1162
+ var ref5, ref6;
1163
+ return (ref6 = (ref5 = (ref = this.args) === null || ref === void 0 ? void 0 : ref.delayMouseStart) !== null && ref5 !== void 0 ? ref5 : (ref4 = this.args) === null || ref4 === void 0 ? void 0 : ref4.delay) !== null && ref6 !== void 0 ? ref6 : 0;
1164
+ }
1165
+ get window() {
1166
+ if (this.context && this.context.window) {
1167
+ return this.context.window;
1168
+ } else if (typeof window !== 'undefined') {
1169
+ return window;
1170
+ }
1171
+ return undefined;
1172
+ }
1173
+ get document() {
1174
+ var ref;
1175
+ if ((ref = this.context) === null || ref === void 0 ? void 0 : ref.document) {
1176
+ return this.context.document;
1177
+ }
1178
+ if (this.window) {
1179
+ return this.window.document;
1180
+ }
1181
+ return undefined;
1182
+ }
1183
+ get rootElement() {
1184
+ var ref;
1185
+ return ((ref = this.args) === null || ref === void 0 ? void 0 : ref.rootElement) || this.document;
1186
+ }
1187
+ constructor(args, context){
1188
+ this.args = args;
1189
+ this.context = context;
1190
+ }
1191
+ }
1192
+
1193
+ function distance(x1, y1, x2, y2) {
1194
+ return Math.sqrt(Math.pow(Math.abs(x2 - x1), 2) + Math.pow(Math.abs(y2 - y1), 2));
1195
+ }
1196
+ function inAngleRanges(x1, y1, x2, y2, angleRanges) {
1197
+ if (!angleRanges) {
1198
+ return false;
1199
+ }
1200
+ const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI + 180;
1201
+ for(let i = 0; i < angleRanges.length; ++i){
1202
+ const ar = angleRanges[i];
1203
+ if (ar && (ar.start == null || angle >= ar.start) && (ar.end == null || angle <= ar.end)) {
1204
+ return true;
1205
+ }
1206
+ }
1207
+ return false;
1208
+ }
1209
+
1210
+ // Used for MouseEvent.buttons (note the s on the end).
1211
+ const MouseButtons = {
1212
+ Left: 1};
1213
+ // Used for e.button (note the lack of an s on the end).
1214
+ const MouseButton = {
1215
+ Left: 0};
1216
+ /**
1217
+ * Only touch events and mouse events where the left button is pressed should initiate a drag.
1218
+ * @param {MouseEvent | TouchEvent} e The event
1219
+ */ function eventShouldStartDrag(e) {
1220
+ // For touch events, button will be undefined. If e.button is defined,
1221
+ // then it should be MouseButton.Left.
1222
+ return e.button === undefined || e.button === MouseButton.Left;
1223
+ }
1224
+ /**
1225
+ * Only touch events and mouse events where the left mouse button is no longer held should end a drag.
1226
+ * It's possible the user mouse downs with the left mouse button, then mouse down and ups with the right mouse button.
1227
+ * We don't want releasing the right mouse button to end the drag.
1228
+ * @param {MouseEvent | TouchEvent} e The event
1229
+ */ function eventShouldEndDrag(e) {
1230
+ // Touch events will have buttons be undefined, while mouse events will have e.buttons's left button
1231
+ // bit field unset if the left mouse button has been released
1232
+ return e.buttons === undefined || (e.buttons & MouseButtons.Left) === 0;
1233
+ }
1234
+ function isTouchEvent(e) {
1235
+ return !!e.targetTouches;
1236
+ }
1237
+
1238
+ const ELEMENT_NODE = 1;
1239
+ function getNodeClientOffset(node) {
1240
+ const el = node.nodeType === ELEMENT_NODE ? node : node.parentElement;
1241
+ if (!el) {
1242
+ return undefined;
25
1243
  }
1244
+ const { top , left } = el.getBoundingClientRect();
26
1245
  return {
27
- customDarkSquareStyle: { backgroundColor: '#b58863' },
28
- customLightSquareStyle: { backgroundColor: '#f0d9b5' },
1246
+ x: left,
1247
+ y: top
29
1248
  };
1249
+ }
1250
+ function getEventClientTouchOffset(e, lastTargetTouchFallback) {
1251
+ if (e.targetTouches.length === 1) {
1252
+ return getEventClientOffset(e.targetTouches[0]);
1253
+ } else if (lastTargetTouchFallback && e.touches.length === 1) {
1254
+ if (e.touches[0].target === lastTargetTouchFallback.target) {
1255
+ return getEventClientOffset(e.touches[0]);
1256
+ }
1257
+ }
1258
+ return;
1259
+ }
1260
+ function getEventClientOffset(e, lastTargetTouchFallback) {
1261
+ if (isTouchEvent(e)) {
1262
+ return getEventClientTouchOffset(e, lastTargetTouchFallback);
1263
+ } else {
1264
+ return {
1265
+ x: e.clientX,
1266
+ y: e.clientY
1267
+ };
1268
+ }
1269
+ }
1270
+
1271
+ const supportsPassive = (()=>{
1272
+ // simular to jQuery's test
1273
+ let supported = false;
1274
+ try {
1275
+ addEventListener('test', ()=>{
1276
+ // do nothing
1277
+ }, Object.defineProperty({}, 'passive', {
1278
+ get () {
1279
+ supported = true;
1280
+ return true;
1281
+ }
1282
+ }));
1283
+ } catch (e) {
1284
+ // do nothing
1285
+ }
1286
+ return supported;
1287
+ })();
1288
+
1289
+ const eventNames = {
1290
+ [ListenerType.mouse]: {
1291
+ start: 'mousedown',
1292
+ move: 'mousemove',
1293
+ end: 'mouseup',
1294
+ contextmenu: 'contextmenu'
1295
+ },
1296
+ [ListenerType.touch]: {
1297
+ start: 'touchstart',
1298
+ move: 'touchmove',
1299
+ end: 'touchend'
1300
+ },
1301
+ [ListenerType.keyboard]: {
1302
+ keydown: 'keydown'
1303
+ }
30
1304
  };
31
- const ThemeProvider = ({ children, theme }) => {
32
- const { customDarkSquareStyle, customLightSquareStyle } = getStylesForTheme(theme);
33
- return (jsxRuntime.jsx(ChessboardThemeContext.Provider, { value: { customDarkSquareStyle, customLightSquareStyle }, children: children }));
1305
+ class TouchBackendImpl {
1306
+ /**
1307
+ * Generate profiling statistics for the HTML5Backend.
1308
+ */ profile() {
1309
+ var ref;
1310
+ return {
1311
+ sourceNodes: this.sourceNodes.size,
1312
+ sourcePreviewNodes: this.sourcePreviewNodes.size,
1313
+ sourcePreviewNodeOptions: this.sourcePreviewNodeOptions.size,
1314
+ targetNodes: this.targetNodes.size,
1315
+ dragOverTargetIds: ((ref = this.dragOverTargetIds) === null || ref === void 0 ? void 0 : ref.length) || 0
1316
+ };
1317
+ }
1318
+ // public for test
1319
+ get document() {
1320
+ return this.options.document;
1321
+ }
1322
+ setup() {
1323
+ const root = this.options.rootElement;
1324
+ if (!root) {
1325
+ return;
1326
+ }
1327
+ invariant(!TouchBackendImpl.isSetUp, 'Cannot have two Touch backends at the same time.');
1328
+ TouchBackendImpl.isSetUp = true;
1329
+ this.addEventListener(root, 'start', this.getTopMoveStartHandler());
1330
+ this.addEventListener(root, 'start', this.handleTopMoveStartCapture, true);
1331
+ this.addEventListener(root, 'move', this.handleTopMove);
1332
+ this.addEventListener(root, 'move', this.handleTopMoveCapture, true);
1333
+ this.addEventListener(root, 'end', this.handleTopMoveEndCapture, true);
1334
+ if (this.options.enableMouseEvents && !this.options.ignoreContextMenu) {
1335
+ this.addEventListener(root, 'contextmenu', this.handleTopMoveEndCapture);
1336
+ }
1337
+ if (this.options.enableKeyboardEvents) {
1338
+ this.addEventListener(root, 'keydown', this.handleCancelOnEscape, true);
1339
+ }
1340
+ }
1341
+ teardown() {
1342
+ const root = this.options.rootElement;
1343
+ if (!root) {
1344
+ return;
1345
+ }
1346
+ TouchBackendImpl.isSetUp = false;
1347
+ this._mouseClientOffset = {};
1348
+ this.removeEventListener(root, 'start', this.handleTopMoveStartCapture, true);
1349
+ this.removeEventListener(root, 'start', this.handleTopMoveStart);
1350
+ this.removeEventListener(root, 'move', this.handleTopMoveCapture, true);
1351
+ this.removeEventListener(root, 'move', this.handleTopMove);
1352
+ this.removeEventListener(root, 'end', this.handleTopMoveEndCapture, true);
1353
+ if (this.options.enableMouseEvents && !this.options.ignoreContextMenu) {
1354
+ this.removeEventListener(root, 'contextmenu', this.handleTopMoveEndCapture);
1355
+ }
1356
+ if (this.options.enableKeyboardEvents) {
1357
+ this.removeEventListener(root, 'keydown', this.handleCancelOnEscape, true);
1358
+ }
1359
+ this.uninstallSourceNodeRemovalObserver();
1360
+ }
1361
+ addEventListener(subject, event, handler, capture = false) {
1362
+ const options = supportsPassive ? {
1363
+ capture,
1364
+ passive: false
1365
+ } : capture;
1366
+ this.listenerTypes.forEach(function(listenerType) {
1367
+ const evt = eventNames[listenerType][event];
1368
+ if (evt) {
1369
+ subject.addEventListener(evt, handler, options);
1370
+ }
1371
+ });
1372
+ }
1373
+ removeEventListener(subject, event, handler, capture = false) {
1374
+ const options = supportsPassive ? {
1375
+ capture,
1376
+ passive: false
1377
+ } : capture;
1378
+ this.listenerTypes.forEach(function(listenerType) {
1379
+ const evt = eventNames[listenerType][event];
1380
+ if (evt) {
1381
+ subject.removeEventListener(evt, handler, options);
1382
+ }
1383
+ });
1384
+ }
1385
+ connectDragSource(sourceId, node) {
1386
+ const handleMoveStart = this.handleMoveStart.bind(this, sourceId);
1387
+ this.sourceNodes.set(sourceId, node);
1388
+ this.addEventListener(node, 'start', handleMoveStart);
1389
+ return ()=>{
1390
+ this.sourceNodes.delete(sourceId);
1391
+ this.removeEventListener(node, 'start', handleMoveStart);
1392
+ };
1393
+ }
1394
+ connectDragPreview(sourceId, node, options) {
1395
+ this.sourcePreviewNodeOptions.set(sourceId, options);
1396
+ this.sourcePreviewNodes.set(sourceId, node);
1397
+ return ()=>{
1398
+ this.sourcePreviewNodes.delete(sourceId);
1399
+ this.sourcePreviewNodeOptions.delete(sourceId);
1400
+ };
1401
+ }
1402
+ connectDropTarget(targetId, node) {
1403
+ const root = this.options.rootElement;
1404
+ if (!this.document || !root) {
1405
+ return ()=>{
1406
+ /* noop */ };
1407
+ }
1408
+ const handleMove = (e)=>{
1409
+ if (!this.document || !root || !this.monitor.isDragging()) {
1410
+ return;
1411
+ }
1412
+ let coords;
1413
+ /**
1414
+ * Grab the coordinates for the current mouse/touch position
1415
+ */ switch(e.type){
1416
+ case eventNames.mouse.move:
1417
+ coords = {
1418
+ x: e.clientX,
1419
+ y: e.clientY
1420
+ };
1421
+ break;
1422
+ case eventNames.touch.move:
1423
+ var ref, ref1;
1424
+ coords = {
1425
+ x: ((ref = e.touches[0]) === null || ref === void 0 ? void 0 : ref.clientX) || 0,
1426
+ y: ((ref1 = e.touches[0]) === null || ref1 === void 0 ? void 0 : ref1.clientY) || 0
1427
+ };
1428
+ break;
1429
+ }
1430
+ /**
1431
+ * Use the coordinates to grab the element the drag ended on.
1432
+ * If the element is the same as the target node (or any of it's children) then we have hit a drop target and can handle the move.
1433
+ */ const droppedOn = coords != null ? this.document.elementFromPoint(coords.x, coords.y) : undefined;
1434
+ const childMatch = droppedOn && node.contains(droppedOn);
1435
+ if (droppedOn === node || childMatch) {
1436
+ return this.handleMove(e, targetId);
1437
+ }
1438
+ };
1439
+ /**
1440
+ * Attaching the event listener to the body so that touchmove will work while dragging over multiple target elements.
1441
+ */ this.addEventListener(this.document.body, 'move', handleMove);
1442
+ this.targetNodes.set(targetId, node);
1443
+ return ()=>{
1444
+ if (this.document) {
1445
+ this.targetNodes.delete(targetId);
1446
+ this.removeEventListener(this.document.body, 'move', handleMove);
1447
+ }
1448
+ };
1449
+ }
1450
+ getTopMoveStartHandler() {
1451
+ if (!this.options.delayTouchStart && !this.options.delayMouseStart) {
1452
+ return this.handleTopMoveStart;
1453
+ }
1454
+ return this.handleTopMoveStartDelay;
1455
+ }
1456
+ installSourceNodeRemovalObserver(node) {
1457
+ this.uninstallSourceNodeRemovalObserver();
1458
+ this.draggedSourceNode = node;
1459
+ this.draggedSourceNodeRemovalObserver = new MutationObserver(()=>{
1460
+ if (node && !node.parentElement) {
1461
+ this.resurrectSourceNode();
1462
+ this.uninstallSourceNodeRemovalObserver();
1463
+ }
1464
+ });
1465
+ if (!node || !node.parentElement) {
1466
+ return;
1467
+ }
1468
+ this.draggedSourceNodeRemovalObserver.observe(node.parentElement, {
1469
+ childList: true
1470
+ });
1471
+ }
1472
+ resurrectSourceNode() {
1473
+ if (this.document && this.draggedSourceNode) {
1474
+ this.draggedSourceNode.style.display = 'none';
1475
+ this.draggedSourceNode.removeAttribute('data-reactid');
1476
+ this.document.body.appendChild(this.draggedSourceNode);
1477
+ }
1478
+ }
1479
+ uninstallSourceNodeRemovalObserver() {
1480
+ if (this.draggedSourceNodeRemovalObserver) {
1481
+ this.draggedSourceNodeRemovalObserver.disconnect();
1482
+ }
1483
+ this.draggedSourceNodeRemovalObserver = undefined;
1484
+ this.draggedSourceNode = undefined;
1485
+ }
1486
+ constructor(manager, context, options){
1487
+ this.getSourceClientOffset = (sourceId)=>{
1488
+ const element = this.sourceNodes.get(sourceId);
1489
+ return element && getNodeClientOffset(element);
1490
+ };
1491
+ this.handleTopMoveStartCapture = (e)=>{
1492
+ if (!eventShouldStartDrag(e)) {
1493
+ return;
1494
+ }
1495
+ this.moveStartSourceIds = [];
1496
+ };
1497
+ this.handleMoveStart = (sourceId)=>{
1498
+ // Just because we received an event doesn't necessarily mean we need to collect drag sources.
1499
+ // We only collect start collecting drag sources on touch and left mouse events.
1500
+ if (Array.isArray(this.moveStartSourceIds)) {
1501
+ this.moveStartSourceIds.unshift(sourceId);
1502
+ }
1503
+ };
1504
+ this.handleTopMoveStart = (e)=>{
1505
+ if (!eventShouldStartDrag(e)) {
1506
+ return;
1507
+ }
1508
+ // Don't prematurely preventDefault() here since it might:
1509
+ // 1. Mess up scrolling
1510
+ // 2. Mess up long tap (which brings up context menu)
1511
+ // 3. If there's an anchor link as a child, tap won't be triggered on link
1512
+ const clientOffset = getEventClientOffset(e);
1513
+ if (clientOffset) {
1514
+ if (isTouchEvent(e)) {
1515
+ this.lastTargetTouchFallback = e.targetTouches[0];
1516
+ }
1517
+ this._mouseClientOffset = clientOffset;
1518
+ }
1519
+ this.waitingForDelay = false;
1520
+ };
1521
+ this.handleTopMoveStartDelay = (e)=>{
1522
+ if (!eventShouldStartDrag(e)) {
1523
+ return;
1524
+ }
1525
+ const delay = e.type === eventNames.touch.start ? this.options.delayTouchStart : this.options.delayMouseStart;
1526
+ this.timeout = setTimeout(this.handleTopMoveStart.bind(this, e), delay);
1527
+ this.waitingForDelay = true;
1528
+ };
1529
+ this.handleTopMoveCapture = ()=>{
1530
+ this.dragOverTargetIds = [];
1531
+ };
1532
+ this.handleMove = (_evt, targetId)=>{
1533
+ if (this.dragOverTargetIds) {
1534
+ this.dragOverTargetIds.unshift(targetId);
1535
+ }
1536
+ };
1537
+ this.handleTopMove = (e1)=>{
1538
+ if (this.timeout) {
1539
+ clearTimeout(this.timeout);
1540
+ }
1541
+ if (!this.document || this.waitingForDelay) {
1542
+ return;
1543
+ }
1544
+ const { moveStartSourceIds , dragOverTargetIds } = this;
1545
+ const enableHoverOutsideTarget = this.options.enableHoverOutsideTarget;
1546
+ const clientOffset = getEventClientOffset(e1, this.lastTargetTouchFallback);
1547
+ if (!clientOffset) {
1548
+ return;
1549
+ }
1550
+ // If the touch move started as a scroll, or is is between the scroll angles
1551
+ if (this._isScrolling || !this.monitor.isDragging() && inAngleRanges(this._mouseClientOffset.x || 0, this._mouseClientOffset.y || 0, clientOffset.x, clientOffset.y, this.options.scrollAngleRanges)) {
1552
+ this._isScrolling = true;
1553
+ return;
1554
+ }
1555
+ // If we're not dragging and we've moved a little, that counts as a drag start
1556
+ if (!this.monitor.isDragging() && // eslint-disable-next-line no-prototype-builtins
1557
+ this._mouseClientOffset.hasOwnProperty('x') && moveStartSourceIds && distance(this._mouseClientOffset.x || 0, this._mouseClientOffset.y || 0, clientOffset.x, clientOffset.y) > (this.options.touchSlop ? this.options.touchSlop : 0)) {
1558
+ this.moveStartSourceIds = undefined;
1559
+ this.actions.beginDrag(moveStartSourceIds, {
1560
+ clientOffset: this._mouseClientOffset,
1561
+ getSourceClientOffset: this.getSourceClientOffset,
1562
+ publishSource: false
1563
+ });
1564
+ }
1565
+ if (!this.monitor.isDragging()) {
1566
+ return;
1567
+ }
1568
+ const sourceNode = this.sourceNodes.get(this.monitor.getSourceId());
1569
+ this.installSourceNodeRemovalObserver(sourceNode);
1570
+ this.actions.publishDragSource();
1571
+ if (e1.cancelable) e1.preventDefault();
1572
+ // Get the node elements of the hovered DropTargets
1573
+ const dragOverTargetNodes = (dragOverTargetIds || []).map((key)=>this.targetNodes.get(key)
1574
+ ).filter((e)=>!!e
1575
+ );
1576
+ // Get the a ordered list of nodes that are touched by
1577
+ const elementsAtPoint = this.options.getDropTargetElementsAtPoint ? this.options.getDropTargetElementsAtPoint(clientOffset.x, clientOffset.y, dragOverTargetNodes) : this.document.elementsFromPoint(clientOffset.x, clientOffset.y);
1578
+ // Extend list with parents that are not receiving elementsFromPoint events (size 0 elements and svg groups)
1579
+ const elementsAtPointExtended = [];
1580
+ for(const nodeId in elementsAtPoint){
1581
+ // eslint-disable-next-line no-prototype-builtins
1582
+ if (!elementsAtPoint.hasOwnProperty(nodeId)) {
1583
+ continue;
1584
+ }
1585
+ let currentNode = elementsAtPoint[nodeId];
1586
+ if (currentNode != null) {
1587
+ elementsAtPointExtended.push(currentNode);
1588
+ }
1589
+ while(currentNode){
1590
+ currentNode = currentNode.parentElement;
1591
+ if (currentNode && elementsAtPointExtended.indexOf(currentNode) === -1) {
1592
+ elementsAtPointExtended.push(currentNode);
1593
+ }
1594
+ }
1595
+ }
1596
+ const orderedDragOverTargetIds = elementsAtPointExtended// Filter off nodes that arent a hovered DropTargets nodes
1597
+ .filter((node)=>dragOverTargetNodes.indexOf(node) > -1
1598
+ )// Map back the nodes elements to targetIds
1599
+ .map((node)=>this._getDropTargetId(node)
1600
+ )// Filter off possible null rows
1601
+ .filter((node)=>!!node
1602
+ ).filter((id, index, ids)=>ids.indexOf(id) === index
1603
+ );
1604
+ // Invoke hover for drop targets when source node is still over and pointer is outside
1605
+ if (enableHoverOutsideTarget) {
1606
+ for(const targetId in this.targetNodes){
1607
+ const targetNode = this.targetNodes.get(targetId);
1608
+ if (sourceNode && targetNode && targetNode.contains(sourceNode) && orderedDragOverTargetIds.indexOf(targetId) === -1) {
1609
+ orderedDragOverTargetIds.unshift(targetId);
1610
+ break;
1611
+ }
1612
+ }
1613
+ }
1614
+ // Reverse order because dnd-core reverse it before calling the DropTarget drop methods
1615
+ orderedDragOverTargetIds.reverse();
1616
+ this.actions.hover(orderedDragOverTargetIds, {
1617
+ clientOffset: clientOffset
1618
+ });
1619
+ };
1620
+ /**
1621
+ *
1622
+ * visible for testing
1623
+ */ this._getDropTargetId = (node)=>{
1624
+ const keys = this.targetNodes.keys();
1625
+ let next = keys.next();
1626
+ while(next.done === false){
1627
+ const targetId = next.value;
1628
+ if (node === this.targetNodes.get(targetId)) {
1629
+ return targetId;
1630
+ } else {
1631
+ next = keys.next();
1632
+ }
1633
+ }
1634
+ return undefined;
1635
+ };
1636
+ this.handleTopMoveEndCapture = (e)=>{
1637
+ this._isScrolling = false;
1638
+ this.lastTargetTouchFallback = undefined;
1639
+ if (!eventShouldEndDrag(e)) {
1640
+ return;
1641
+ }
1642
+ if (!this.monitor.isDragging() || this.monitor.didDrop()) {
1643
+ this.moveStartSourceIds = undefined;
1644
+ return;
1645
+ }
1646
+ if (e.cancelable) e.preventDefault();
1647
+ this._mouseClientOffset = {};
1648
+ this.uninstallSourceNodeRemovalObserver();
1649
+ this.actions.drop();
1650
+ this.actions.endDrag();
1651
+ };
1652
+ this.handleCancelOnEscape = (e)=>{
1653
+ if (e.key === 'Escape' && this.monitor.isDragging()) {
1654
+ this._mouseClientOffset = {};
1655
+ this.uninstallSourceNodeRemovalObserver();
1656
+ this.actions.endDrag();
1657
+ }
1658
+ };
1659
+ this.options = new OptionsReader(options, context);
1660
+ this.actions = manager.getActions();
1661
+ this.monitor = manager.getMonitor();
1662
+ this.sourceNodes = new Map();
1663
+ this.sourcePreviewNodes = new Map();
1664
+ this.sourcePreviewNodeOptions = new Map();
1665
+ this.targetNodes = new Map();
1666
+ this.listenerTypes = [];
1667
+ this._mouseClientOffset = {};
1668
+ this._isScrolling = false;
1669
+ if (this.options.enableMouseEvents) {
1670
+ this.listenerTypes.push(ListenerType.mouse);
1671
+ }
1672
+ if (this.options.enableTouchEvents) {
1673
+ this.listenerTypes.push(ListenerType.touch);
1674
+ }
1675
+ if (this.options.enableKeyboardEvents) {
1676
+ this.listenerTypes.push(ListenerType.keyboard);
1677
+ }
1678
+ }
1679
+ }
1680
+
1681
+ const TouchBackend = function createBackend(manager, context = {}, options = {}) {
1682
+ return new TouchBackendImpl(manager, context, options);
34
1683
  };
35
1684
 
1685
+ var c={backends:[{id:"html5",backend:HTML5Backend,transition:U},{id:"touch",backend:TouchBackend,options:{enableMouseEvents:true},preview:true,transition:L}]};
1686
+
1687
+ /**
1688
+ * HTML5 for mouse (desktop + Chrome device emulation), touch backend for real
1689
+ * mobile devices. react-chessboard alone picks TouchBackend whenever
1690
+ * `ontouchstart` is in window, which breaks mouse drags in DevTools emulation.
1691
+ */
1692
+ const ChessboardDnDProvider = ({ children, }) => (jsxRuntime.jsx(reactChessboard.ChessboardDnDProvider, { backend: S, options: c, children: children }));
1693
+
36
1694
  /******************************************************************************
37
1695
  Copyright (c) Microsoft Corporation.
38
1696
 
@@ -553,23 +2211,215 @@ class StockfishBrowserEngine {
553
2211
  if (generation !== this.analysisGeneration) {
554
2212
  return;
555
2213
  }
556
- const withFen = this.analysisFen.length > 0
557
- ? Object.assign(Object.assign({}, evaluation), { fen: this.analysisFen }) : evaluation;
558
- this.evaluation = withFen;
559
- this.listeners.forEach((listener) => listener(withFen));
560
- }
561
- }
2214
+ const withFen = this.analysisFen.length > 0
2215
+ ? Object.assign(Object.assign({}, evaluation), { fen: this.analysisFen }) : evaluation;
2216
+ this.evaluation = withFen;
2217
+ this.listeners.forEach((listener) => listener(withFen));
2218
+ }
2219
+ }
2220
+
2221
+ const AnalysisEngineContext = react.createContext(null);
2222
+ const useAnalysisEngineContext = () => react.useContext(AnalysisEngineContext);
2223
+ const normalizeSubscriberOptions = (fen, options = {}) => {
2224
+ var _a, _b, _c, _d;
2225
+ return ({
2226
+ fen,
2227
+ enabled: (_a = options.enabled) !== null && _a !== void 0 ? _a : true,
2228
+ depth: (_b = options.depth) !== null && _b !== void 0 ? _b : 16,
2229
+ multiPv: (_c = options.multiPv) !== null && _c !== void 0 ? _c : 2,
2230
+ priority: (_d = options.priority) !== null && _d !== void 0 ? _d : 0,
2231
+ });
2232
+ };
2233
+ const analyzingForFen = (fen, evaluation) => (Object.assign(Object.assign({}, emptyEngineEvaluation()), { status: evaluation.status === 'error'
2234
+ ? 'error'
2235
+ : evaluation.status === 'loading'
2236
+ ? 'loading'
2237
+ : 'analyzing', error: evaluation.error, fen }));
2238
+ const AnalysisEngineProvider = ({ scriptUrl = DEFAULT_STOCKFISH_SCRIPT_URL, children, }) => {
2239
+ const engineRef = react.useRef(null);
2240
+ const subscribersRef = react.useRef(new Map());
2241
+ const nextIdRef = react.useRef(0);
2242
+ const activeKeyRef = react.useRef('');
2243
+ const readyRef = react.useRef(false);
2244
+ const scheduleRef = react.useRef(null);
2245
+ const lastEvaluationRef = react.useRef(emptyEngineEvaluation());
2246
+ const pickActive = react.useCallback(() => {
2247
+ const enabled = [...subscribersRef.current.values()].filter((subscriber) => subscriber.options.enabled);
2248
+ if (enabled.length === 0) {
2249
+ return null;
2250
+ }
2251
+ return enabled.sort((left, right) => right.options.priority - left.options.priority)[0];
2252
+ }, []);
2253
+ const notifyAll = react.useCallback((evaluation) => {
2254
+ lastEvaluationRef.current = evaluation;
2255
+ const active = pickActive();
2256
+ for (const subscriber of subscribersRef.current.values()) {
2257
+ const { fen, enabled } = subscriber.options;
2258
+ if (!enabled) {
2259
+ subscriber.listener(emptyEngineEvaluation());
2260
+ continue;
2261
+ }
2262
+ if (evaluation.fen && evaluation.fen === fen) {
2263
+ subscriber.listener(evaluation);
2264
+ continue;
2265
+ }
2266
+ if ((active === null || active === void 0 ? void 0 : active.id) === subscriber.id) {
2267
+ subscriber.listener(analyzingForFen(fen, evaluation));
2268
+ }
2269
+ }
2270
+ }, [pickActive]);
2271
+ const syncAnalysis = react.useCallback(() => {
2272
+ const engine = engineRef.current;
2273
+ if (!engine || !readyRef.current) {
2274
+ return;
2275
+ }
2276
+ const active = pickActive();
2277
+ if (!active) {
2278
+ activeKeyRef.current = '';
2279
+ engine.stop();
2280
+ notifyAll(emptyEngineEvaluation());
2281
+ return;
2282
+ }
2283
+ const { fen, depth, multiPv } = active.options;
2284
+ const key = `${active.id}|${fen}|${depth}|${multiPv}`;
2285
+ if (key === activeKeyRef.current) {
2286
+ return;
2287
+ }
2288
+ activeKeyRef.current = key;
2289
+ if (scheduleRef.current !== null) {
2290
+ window.clearTimeout(scheduleRef.current);
2291
+ }
2292
+ scheduleRef.current = window.setTimeout(() => {
2293
+ scheduleRef.current = null;
2294
+ engine.analyze(fen, depth, multiPv);
2295
+ }, 75);
2296
+ }, [notifyAll, pickActive]);
2297
+ react.useEffect(() => {
2298
+ if (typeof Worker === 'undefined') {
2299
+ return;
2300
+ }
2301
+ const engine = new StockfishBrowserEngine(scriptUrl);
2302
+ engineRef.current = engine;
2303
+ let cancelled = false;
2304
+ const unsubscribe = engine.subscribe((evaluation) => {
2305
+ if (!cancelled) {
2306
+ notifyAll(evaluation);
2307
+ }
2308
+ });
2309
+ engine
2310
+ .init()
2311
+ .then(() => {
2312
+ if (cancelled) {
2313
+ return;
2314
+ }
2315
+ readyRef.current = true;
2316
+ syncAnalysis();
2317
+ })
2318
+ .catch((error) => {
2319
+ if (cancelled) {
2320
+ return;
2321
+ }
2322
+ const message = error instanceof Error ? error.message : 'Failed to start engine';
2323
+ notifyAll(Object.assign(Object.assign({}, emptyEngineEvaluation()), { status: 'error', error: message }));
2324
+ });
2325
+ return () => {
2326
+ cancelled = true;
2327
+ readyRef.current = false;
2328
+ activeKeyRef.current = '';
2329
+ if (scheduleRef.current !== null) {
2330
+ window.clearTimeout(scheduleRef.current);
2331
+ scheduleRef.current = null;
2332
+ }
2333
+ unsubscribe();
2334
+ engine.dispose();
2335
+ engineRef.current = null;
2336
+ };
2337
+ }, [notifyAll, scriptUrl, syncAnalysis]);
2338
+ const register = react.useCallback((options, listener) => {
2339
+ const id = ++nextIdRef.current;
2340
+ subscribersRef.current.set(id, { id, options, listener });
2341
+ if (options.enabled) {
2342
+ const active = pickActive();
2343
+ if ((active === null || active === void 0 ? void 0 : active.id) === id) {
2344
+ listener(analyzingForFen(options.fen, lastEvaluationRef.current));
2345
+ }
2346
+ }
2347
+ else {
2348
+ listener(emptyEngineEvaluation());
2349
+ }
2350
+ syncAnalysis();
2351
+ return id;
2352
+ }, [pickActive, syncAnalysis]);
2353
+ const update = react.useCallback((id, options) => {
2354
+ const subscriber = subscribersRef.current.get(id);
2355
+ if (!subscriber) {
2356
+ return;
2357
+ }
2358
+ subscriber.options = options;
2359
+ if (!options.enabled) {
2360
+ subscriber.listener(emptyEngineEvaluation());
2361
+ }
2362
+ else {
2363
+ const active = pickActive();
2364
+ if ((active === null || active === void 0 ? void 0 : active.id) === id) {
2365
+ subscriber.listener(analyzingForFen(options.fen, lastEvaluationRef.current));
2366
+ }
2367
+ }
2368
+ activeKeyRef.current = '';
2369
+ syncAnalysis();
2370
+ }, [pickActive, syncAnalysis]);
2371
+ const unregister = react.useCallback((id) => {
2372
+ subscribersRef.current.delete(id);
2373
+ activeKeyRef.current = '';
2374
+ syncAnalysis();
2375
+ }, [syncAnalysis]);
2376
+ const value = react.useMemo(() => ({
2377
+ register,
2378
+ update,
2379
+ unregister,
2380
+ }), [register, unregister, update]);
2381
+ return (jsxRuntime.jsx(AnalysisEngineContext.Provider, { value: value, children: children }));
2382
+ };
562
2383
 
563
2384
  const useAnalysisEngine = (fen, options = {}) => {
564
- const { enabled = true, depth = 16, multiPv = 2, scriptUrl = DEFAULT_STOCKFISH_SCRIPT_URL, } = options;
2385
+ var _a;
2386
+ const context = useAnalysisEngineContext();
2387
+ const useShared = ((_a = options.shared) !== null && _a !== void 0 ? _a : true) && context !== null;
2388
+ const { enabled = true, depth = 16, multiPv = 2, priority = 0, scriptUrl = DEFAULT_STOCKFISH_SCRIPT_URL, } = options;
565
2389
  const [evaluation, setEvaluation] = react.useState(emptyEngineEvaluation());
566
2390
  const [engineReady, setEngineReady] = react.useState(false);
567
2391
  const engineRef = react.useRef(null);
568
2392
  const mountGenerationRef = react.useRef(0);
2393
+ const subscriberIdRef = react.useRef(null);
2394
+ const subscriberOptions = react.useMemo(() => normalizeSubscriberOptions(fen, options), [fen, enabled, depth, multiPv, priority]);
2395
+ react.useLayoutEffect(() => {
2396
+ if (!useShared || !context) {
2397
+ return;
2398
+ }
2399
+ if (subscriberIdRef.current === null) {
2400
+ subscriberIdRef.current = context.register(subscriberOptions, setEvaluation);
2401
+ return;
2402
+ }
2403
+ context.update(subscriberIdRef.current, subscriberOptions);
2404
+ }, [context, subscriberOptions, useShared]);
569
2405
  react.useEffect(() => {
570
- if (!enabled || typeof Worker === 'undefined') {
571
- setEvaluation(emptyEngineEvaluation());
572
- setEngineReady(false);
2406
+ if (!useShared || !context) {
2407
+ return;
2408
+ }
2409
+ const contextValue = context;
2410
+ return () => {
2411
+ if (subscriberIdRef.current !== null) {
2412
+ contextValue.unregister(subscriberIdRef.current);
2413
+ subscriberIdRef.current = null;
2414
+ }
2415
+ };
2416
+ }, [context, useShared]);
2417
+ react.useEffect(() => {
2418
+ if (useShared || !enabled || typeof Worker === 'undefined') {
2419
+ if (!useShared && !enabled) {
2420
+ setEvaluation(emptyEngineEvaluation());
2421
+ setEngineReady(false);
2422
+ }
573
2423
  return;
574
2424
  }
575
2425
  const mountGeneration = ++mountGenerationRef.current;
@@ -605,9 +2455,9 @@ const useAnalysisEngine = (fen, options = {}) => {
605
2455
  engineRef.current = null;
606
2456
  }
607
2457
  };
608
- }, [enabled, scriptUrl]);
2458
+ }, [enabled, scriptUrl, useShared]);
609
2459
  react.useLayoutEffect(() => {
610
- if (!enabled || !engineReady || !engineRef.current) {
2460
+ if (useShared || !enabled || !engineReady || !engineRef.current) {
611
2461
  return;
612
2462
  }
613
2463
  const engine = engineRef.current;
@@ -617,7 +2467,7 @@ const useAnalysisEngine = (fen, options = {}) => {
617
2467
  return () => {
618
2468
  window.clearTimeout(timer);
619
2469
  };
620
- }, [enabled, engineReady, fen, depth, multiPv]);
2470
+ }, [useShared, enabled, engineReady, fen, depth, multiPv]);
621
2471
  return react.useMemo(() => {
622
2472
  if (evaluation.fen !== fen) {
623
2473
  return Object.assign(Object.assign({}, emptyEngineEvaluation()), { status: evaluation.status === 'error'
@@ -702,10 +2552,151 @@ class AnalysisErrorBoundary extends react.Component {
702
2552
  }
703
2553
  }
704
2554
 
2555
+ const navRowStyle = {
2556
+ display: 'flex',
2557
+ alignItems: 'center',
2558
+ gap: 6,
2559
+ };
2560
+ const scrubberInputStyle = {
2561
+ flex: 1,
2562
+ };
2563
+ const plyLabelStyle = {
2564
+ minWidth: 56,
2565
+ textAlign: 'center',
2566
+ fontSize: 14,
2567
+ };
2568
+ function palette(theme) {
2569
+ return {
2570
+ text: theme === 'dark' ? '#e8e8e8' : '#1a1a1a',
2571
+ border: theme === 'dark' ? '#3a3a3a' : '#d0d0d0',
2572
+ surface: theme === 'dark' ? '#262626' : '#f5f5f5',
2573
+ };
2574
+ }
2575
+ function navButtonStyle(colors) {
2576
+ return {
2577
+ padding: '4px 10px',
2578
+ borderRadius: 6,
2579
+ cursor: 'pointer',
2580
+ fontSize: 14,
2581
+ fontWeight: 600,
2582
+ border: `1px solid ${colors.border}`,
2583
+ background: colors.surface,
2584
+ color: colors.text,
2585
+ };
2586
+ }
2587
+
2588
+ /** Library-default ply navigation (inline styles). */
2589
+ const DefaultPlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPrev, onGoNext, onGoLast, onGoTo, theme, showScrubber, showPlyLabel, }) => {
2590
+ const colors = palette(theme);
2591
+ const buttonStyle = navButtonStyle(colors);
2592
+ return (jsxRuntime.jsxs("div", { style: navRowStyle, children: [jsxRuntime.jsx("button", { type: "button", onClick: onGoFirst, disabled: !canPrev, style: buttonStyle, "aria-label": "First move", children: "\u23EE" }), jsxRuntime.jsx("button", { type: "button", onClick: onGoPrev, disabled: !canPrev, style: buttonStyle, "aria-label": "Previous move", children: "\u25C0" }), showScrubber ? (jsxRuntime.jsx("input", { type: "range", min: 0, max: totalPly, value: plyIndex, onChange: (e) => onGoTo(Number(e.target.value)), style: scrubberInputStyle, "aria-label": "Scrub through game" })) : showPlyLabel ? (jsxRuntime.jsxs("span", { style: Object.assign(Object.assign({}, plyLabelStyle), { color: colors.text }), children: [plyIndex, " / ", totalPly] })) : null, jsxRuntime.jsx("button", { type: "button", onClick: onGoNext, disabled: !canNext, style: buttonStyle, "aria-label": "Next move", children: "\u25B6" }), jsxRuntime.jsx("button", { type: "button", onClick: onGoLast, disabled: !canNext, style: buttonStyle, "aria-label": "Last move", children: "\u23ED" })] }));
2593
+ };
2594
+ const defaultRenderPlyNavigation = (props) => (jsxRuntime.jsx(DefaultPlyNavigation, Object.assign({}, props)));
2595
+
2596
+ /** True when the event target is a field where arrow keys should type, not navigate. */
2597
+ function isEditableKeyboardTarget(target) {
2598
+ if (!(target instanceof HTMLElement)) {
2599
+ return false;
2600
+ }
2601
+ if (target.isContentEditable) {
2602
+ return true;
2603
+ }
2604
+ const tag = target.tagName;
2605
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
2606
+ }
2607
+
2608
+ /**
2609
+ * Global keyboard shortcuts for browsing positions:
2610
+ * - ArrowLeft: previous
2611
+ * - ArrowRight: next
2612
+ * - Home: first (when {@link PositionKeyboardNavOptions.onFirst} is provided)
2613
+ * - End: last (when {@link PositionKeyboardNavOptions.onLast} is provided)
2614
+ *
2615
+ * Ignores keypresses while focus is in an input, textarea, select, or
2616
+ * contenteditable element, and ignores modified keys (Alt/Ctrl/Meta).
2617
+ */
2618
+ function usePositionKeyboardNav({ enabled = true, canPrev, canNext, onPrev, onNext, onFirst, onLast, }) {
2619
+ react.useEffect(() => {
2620
+ if (!enabled) {
2621
+ return;
2622
+ }
2623
+ const handleKeyDown = (event) => {
2624
+ if (isEditableKeyboardTarget(event.target)) {
2625
+ return;
2626
+ }
2627
+ if (event.altKey || event.ctrlKey || event.metaKey) {
2628
+ return;
2629
+ }
2630
+ switch (event.key) {
2631
+ case 'ArrowLeft':
2632
+ if (!canPrev) {
2633
+ return;
2634
+ }
2635
+ event.preventDefault();
2636
+ onPrev();
2637
+ break;
2638
+ case 'ArrowRight':
2639
+ if (!canNext) {
2640
+ return;
2641
+ }
2642
+ event.preventDefault();
2643
+ onNext();
2644
+ break;
2645
+ case 'Home':
2646
+ if (!onFirst || !canPrev) {
2647
+ return;
2648
+ }
2649
+ event.preventDefault();
2650
+ onFirst();
2651
+ break;
2652
+ case 'End':
2653
+ if (!onLast || !canNext) {
2654
+ return;
2655
+ }
2656
+ event.preventDefault();
2657
+ onLast();
2658
+ break;
2659
+ }
2660
+ };
2661
+ window.addEventListener('keydown', handleKeyDown);
2662
+ return () => window.removeEventListener('keydown', handleKeyDown);
2663
+ }, [enabled, canPrev, canNext, onPrev, onNext, onFirst, onLast]);
2664
+ }
2665
+
2666
+ /**
2667
+ * Step through a fixed move list. Omit {@link PlyNavigationProps.renderPlyNavigation}
2668
+ * for the default inline-styled UI, or pass a custom renderer (e.g. MUI controls).
2669
+ */
2670
+ const PlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPrev, onGoNext, onGoLast, onGoTo, theme = 'dark', keyboardNav = true, showScrubber = true, showPlyLabel, renderPlyNavigation = defaultRenderPlyNavigation, }) => {
2671
+ usePositionKeyboardNav({
2672
+ enabled: keyboardNav,
2673
+ canPrev,
2674
+ canNext,
2675
+ onPrev: onGoPrev,
2676
+ onNext: onGoNext,
2677
+ onFirst: onGoFirst,
2678
+ onLast: onGoLast,
2679
+ });
2680
+ return renderPlyNavigation({
2681
+ plyIndex,
2682
+ totalPly,
2683
+ canPrev,
2684
+ canNext,
2685
+ onGoFirst,
2686
+ onGoPrev,
2687
+ onGoNext,
2688
+ onGoLast,
2689
+ onGoTo,
2690
+ theme,
2691
+ showScrubber,
2692
+ showPlyLabel: showPlyLabel !== null && showPlyLabel !== void 0 ? showPlyLabel : !showScrubber,
2693
+ });
2694
+ };
2695
+
705
2696
  /** Draggable analysis board (no surrounding layout chrome). */
706
2697
  const AnalysisChessboardView = ({ model }) => {
707
2698
  var _a;
708
- return (jsxRuntime.jsx(reactChessboard.ChessboardDnDProvider, { children: jsxRuntime.jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", customSquareStyles: model.lastMove
2699
+ return (jsxRuntime.jsx(ChessboardDnDProvider, { children: jsxRuntime.jsx(HighlightChessboard, { checkSquare: (_a = model.checkSquare) !== null && _a !== void 0 ? _a : '', hintSquare: null, incorrectMoveSquare: null, position: model.fen, boardOrientation: model.boardOrientation, boardWidth: model.boardWidth, arePiecesDraggable: true, onPieceDrop: model.onPieceDrop, promotionDialogVariant: "modal", customSquareStyles: model.lastMove
709
2700
  ? getLastMoveSquareStyles(model.lastMove.from, model.lastMove.to, model.theme)
710
2701
  : {} }) }));
711
2702
  };
@@ -1055,12 +3046,28 @@ const useAnalysisBoardModel = ({ analysisContext, onClose, theme, boardWidth, en
1055
3046
  * No layout divs — use {@link renderMain} (e.g. `AnalysisBoardLayout` from `analysis/defaults` or a host layout).
1056
3047
  */
1057
3048
  const AnalysisBoardCore = (_a) => {
1058
- var { renderContainer, renderMain, renderSidebar, renderEngineEvaluation } = _a, modelArgs = __rest(_a, ["renderContainer", "renderMain", "renderSidebar", "renderEngineEvaluation"]);
3049
+ var { renderContainer, renderMain, renderSidebar, renderEngineEvaluation, keyboardNav = true } = _a, modelArgs = __rest(_a, ["renderContainer", "renderMain", "renderSidebar", "renderEngineEvaluation", "keyboardNav"]);
1059
3050
  const model = useAnalysisBoardModel(modelArgs);
1060
- return (jsxRuntime.jsx(AnalysisBoardCoreView, { model: model, renderContainer: renderContainer, renderMain: renderMain, renderSidebar: renderSidebar, renderEngineEvaluation: renderEngineEvaluation }));
3051
+ return (jsxRuntime.jsx(AnalysisBoardCoreView, { model: model, keyboardNav: keyboardNav, renderContainer: renderContainer, renderMain: renderMain, renderSidebar: renderSidebar, renderEngineEvaluation: renderEngineEvaluation }));
1061
3052
  };
1062
3053
  /** Pure composition (no layout styles) for testing and reuse. */
1063
- const AnalysisBoardCoreView = ({ model, renderContainer, renderMain, renderSidebar, renderEngineEvaluation, }) => {
3054
+ const AnalysisBoardCoreView = ({ model, keyboardNav, renderContainer, renderMain, renderSidebar, renderEngineEvaluation, }) => {
3055
+ const { ply, maxPly, onSelectPly } = model;
3056
+ const canPrev = ply > 0;
3057
+ const canNext = ply < maxPly;
3058
+ const goFirst = react.useCallback(() => onSelectPly(0), [onSelectPly]);
3059
+ const goPrev = react.useCallback(() => onSelectPly(ply - 1), [onSelectPly, ply]);
3060
+ const goNext = react.useCallback(() => onSelectPly(ply + 1), [onSelectPly, ply]);
3061
+ const goLast = react.useCallback(() => onSelectPly(maxPly), [maxPly, onSelectPly]);
3062
+ usePositionKeyboardNav({
3063
+ enabled: keyboardNav,
3064
+ canPrev,
3065
+ canNext,
3066
+ onPrev: goPrev,
3067
+ onNext: goNext,
3068
+ onFirst: goFirst,
3069
+ onLast: goLast,
3070
+ });
1064
3071
  const board = jsxRuntime.jsx(AnalysisChessboardView, { model: model });
1065
3072
  const engineEvaluationPanel = model.engineEnabled
1066
3073
  ? renderEngineEvaluation({
@@ -1328,66 +3335,6 @@ const closeButtonStyle = {
1328
3335
  fontSize: 14,
1329
3336
  };
1330
3337
 
1331
- const navRowStyle = {
1332
- display: 'flex',
1333
- alignItems: 'center',
1334
- gap: 6,
1335
- };
1336
- const scrubberInputStyle = {
1337
- flex: 1,
1338
- };
1339
- const plyLabelStyle = {
1340
- minWidth: 56,
1341
- textAlign: 'center',
1342
- fontSize: 14,
1343
- };
1344
- function palette(theme) {
1345
- return {
1346
- text: theme === 'dark' ? '#e8e8e8' : '#1a1a1a',
1347
- border: theme === 'dark' ? '#3a3a3a' : '#d0d0d0',
1348
- surface: theme === 'dark' ? '#262626' : '#f5f5f5',
1349
- };
1350
- }
1351
- function navButtonStyle(colors) {
1352
- return {
1353
- padding: '4px 10px',
1354
- borderRadius: 6,
1355
- cursor: 'pointer',
1356
- fontSize: 14,
1357
- fontWeight: 600,
1358
- border: `1px solid ${colors.border}`,
1359
- background: colors.surface,
1360
- color: colors.text,
1361
- };
1362
- }
1363
-
1364
- /** Library-default ply navigation (inline styles). */
1365
- const DefaultPlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPrev, onGoNext, onGoLast, onGoTo, theme, showScrubber, showPlyLabel, }) => {
1366
- const colors = palette(theme);
1367
- const buttonStyle = navButtonStyle(colors);
1368
- return (jsxRuntime.jsxs("div", { style: navRowStyle, children: [jsxRuntime.jsx("button", { type: "button", onClick: onGoFirst, disabled: !canPrev, style: buttonStyle, "aria-label": "First move", children: "\u23EE" }), jsxRuntime.jsx("button", { type: "button", onClick: onGoPrev, disabled: !canPrev, style: buttonStyle, "aria-label": "Previous move", children: "\u25C0" }), showScrubber ? (jsxRuntime.jsx("input", { type: "range", min: 0, max: totalPly, value: plyIndex, onChange: (e) => onGoTo(Number(e.target.value)), style: scrubberInputStyle, "aria-label": "Scrub through game" })) : showPlyLabel ? (jsxRuntime.jsxs("span", { style: Object.assign(Object.assign({}, plyLabelStyle), { color: colors.text }), children: [plyIndex, " / ", totalPly] })) : null, jsxRuntime.jsx("button", { type: "button", onClick: onGoNext, disabled: !canNext, style: buttonStyle, "aria-label": "Next move", children: "\u25B6" }), jsxRuntime.jsx("button", { type: "button", onClick: onGoLast, disabled: !canNext, style: buttonStyle, "aria-label": "Last move", children: "\u23ED" })] }));
1369
- };
1370
- const defaultRenderPlyNavigation = (props) => (jsxRuntime.jsx(DefaultPlyNavigation, Object.assign({}, props)));
1371
-
1372
- /**
1373
- * Step through a fixed move list. Omit {@link PlyNavigationProps.renderPlyNavigation}
1374
- * for the default inline-styled UI, or pass a custom renderer (e.g. MUI controls).
1375
- */
1376
- const PlyNavigation = ({ plyIndex, totalPly, canPrev, canNext, onGoFirst, onGoPrev, onGoNext, onGoLast, onGoTo, theme = 'dark', showScrubber = true, showPlyLabel, renderPlyNavigation = defaultRenderPlyNavigation, }) => renderPlyNavigation({
1377
- plyIndex,
1378
- totalPly,
1379
- canPrev,
1380
- canNext,
1381
- onGoFirst,
1382
- onGoPrev,
1383
- onGoNext,
1384
- onGoLast,
1385
- onGoTo,
1386
- theme,
1387
- showScrubber,
1388
- showPlyLabel: showPlyLabel !== null && showPlyLabel !== void 0 ? showPlyLabel : !showScrubber,
1389
- });
1390
-
1391
3338
  const DefaultAnalysisSidebar = ({ historyRows, isHistoryRowSelected, onSelectHistoryRow, ply, maxPly, onSelectPly, theme, engineEvaluationPanel, }) => {
1392
3339
  const rowBands = createSidebarRowBandCounters();
1393
3340
  const baseChipStyle = {
@@ -1395,7 +3342,7 @@ const DefaultAnalysisSidebar = ({ historyRows, isHistoryRowSelected, onSelectHis
1395
3342
  padding: '4px 8px',
1396
3343
  borderRadius: 4,
1397
3344
  };
1398
- return (jsxRuntime.jsxs("div", { style: sidebarStyle, children: [jsxRuntime.jsxs("div", { style: navBlockStyle, children: [jsxRuntime.jsx(PlyNavigation, { plyIndex: ply, totalPly: maxPly, canPrev: ply > 0, canNext: ply < maxPly, onGoFirst: () => onSelectPly(0), onGoPrev: () => onSelectPly(ply - 1), onGoNext: () => onSelectPly(ply + 1), onGoLast: () => onSelectPly(maxPly), onGoTo: onSelectPly, theme: theme, showScrubber: false }), jsxRuntime.jsx("p", { style: sectionTitleStyle, children: "Move history" })] }), jsxRuntime.jsxs("div", { style: contentRowStyle, children: [jsxRuntime.jsx("ol", { style: moveListStyle, children: historyRows.length === 0 ? (jsxRuntime.jsx("li", { style: emptyRowStyle, children: "No moves played yet." })) : (historyRows.map((row) => {
3345
+ return (jsxRuntime.jsxs("div", { style: sidebarStyle, children: [jsxRuntime.jsxs("div", { style: navBlockStyle, children: [jsxRuntime.jsx(PlyNavigation, { plyIndex: ply, totalPly: maxPly, canPrev: ply > 0, canNext: ply < maxPly, onGoFirst: () => onSelectPly(0), onGoPrev: () => onSelectPly(ply - 1), onGoNext: () => onSelectPly(ply + 1), onGoLast: () => onSelectPly(maxPly), onGoTo: onSelectPly, theme: theme, keyboardNav: false, showScrubber: false }), jsxRuntime.jsx("p", { style: sectionTitleStyle, children: "Move history" })] }), jsxRuntime.jsxs("div", { style: contentRowStyle, children: [jsxRuntime.jsx("ol", { style: moveListStyle, "aria-label": "Move history", children: historyRows.length === 0 ? (jsxRuntime.jsx("li", { style: emptyRowStyle, children: "No moves played yet." })) : (historyRows.map((row) => {
1399
3346
  const isSelected = isHistoryRowSelected(row);
1400
3347
  const isVariation = row.kind === 'variation';
1401
3348
  const backgroundColor = isSelected
@@ -1494,54 +3441,456 @@ const AnalysisBoard = (_a) => {
1494
3441
  : () => null) })));
1495
3442
  };
1496
3443
 
3444
+ /** Resolve a board drag into a legal UCI string, or null when illegal. */
3445
+ function uciFromDrop(fen, sourceSquare, targetSquare, piece) {
3446
+ var _a, _b;
3447
+ const chess = new chess_js.Chess(fen);
3448
+ const pieceType = (_a = piece[1]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
3449
+ const legal = chess
3450
+ .moves({ square: sourceSquare, verbose: true })
3451
+ .find((move) => move.to === targetSquare &&
3452
+ (!move.promotion || move.promotion === pieceType));
3453
+ if (!legal)
3454
+ return null;
3455
+ return `${legal.from}${legal.to}${(_b = legal.promotion) !== null && _b !== void 0 ? _b : ''}`;
3456
+ }
3457
+ function matchesExpectedUci(uci, expectedUci) {
3458
+ return uci.toLowerCase() === expectedUci.toLowerCase();
3459
+ }
3460
+
3461
+ /**
3462
+ * Evaluate a training drop without mutating board position.
3463
+ * Returns `false` for incorrect attempts so react-chessboard snaps the piece back.
3464
+ */
3465
+ function evaluateExpectedMoveDrop(fen, sourceSquare, targetSquare, piece, expectedUci, enabled) {
3466
+ if (!enabled || !expectedUci) {
3467
+ return { kind: 'ignored' };
3468
+ }
3469
+ const uci = uciFromDrop(fen, sourceSquare, targetSquare, piece);
3470
+ if (!uci) {
3471
+ return { kind: 'illegal' };
3472
+ }
3473
+ if (matchesExpectedUci(uci, expectedUci)) {
3474
+ return { kind: 'correct', uci };
3475
+ }
3476
+ return { kind: 'incorrect', uci };
3477
+ }
3478
+ function createExpectedMoveDropHandler({ fen, expectedUci, enabled, onCorrect, onIncorrect, }) {
3479
+ return (sourceSquare, targetSquare, piece) => {
3480
+ const result = evaluateExpectedMoveDrop(fen, sourceSquare, targetSquare, piece, expectedUci, enabled);
3481
+ switch (result.kind) {
3482
+ case 'correct':
3483
+ onCorrect(result.uci);
3484
+ return true;
3485
+ case 'incorrect':
3486
+ onIncorrect(result.uci);
3487
+ return false;
3488
+ default:
3489
+ return false;
3490
+ }
3491
+ };
3492
+ }
3493
+
3494
+ /**
3495
+ * Bump the revision counter to force a controlled chessboard re-render after a
3496
+ * rejected drop. Pair with returning `false` from `onPieceDrop` so the board
3497
+ * snaps back without changing the controlled `position` FEN.
3498
+ */
3499
+ function useBoardRevision() {
3500
+ const [revision, setRevision] = react.useState(0);
3501
+ const bumpRevision = react.useCallback(() => {
3502
+ setRevision((current) => current + 1);
3503
+ }, []);
3504
+ return { revision, bumpRevision };
3505
+ }
3506
+
3507
+ /** Minimum eval loss (pawns) from the wrong move before showing a refutation. */
3508
+ const REFUTATION_EVAL_GAP_PAWNS = 0.5;
3509
+ const REFUTATION_EVAL_GAP_CP = REFUTATION_EVAL_GAP_PAWNS * 100;
3510
+ const refutationEngineOptions = {
3511
+ depth: 14,
3512
+ multiPv: 1,
3513
+ };
3514
+ function fenAfterUci(fen, uci) {
3515
+ const chess = new chess_js.Chess(fen);
3516
+ if (!applyUciMove(chess, uci)) {
3517
+ return null;
3518
+ }
3519
+ return chess.fen();
3520
+ }
3521
+ /** Centipawn score from side to move, comparable across sibling positions. */
3522
+ function lineEvalCpForGap(line) {
3523
+ if (!line) {
3524
+ return null;
3525
+ }
3526
+ if (line.mate !== null) {
3527
+ return line.mate > 0 ? 10000 - line.mate : -1e4 + line.mate;
3528
+ }
3529
+ return line.centipawns;
3530
+ }
3531
+ /** How much better the opponent's eval is after the wrong move vs the correct one. */
3532
+ function refutationEvalGapCp(evalAfterWrong, evalAfterCorrect) {
3533
+ const wrongCp = lineEvalCpForGap(evalAfterWrong.lines[0]);
3534
+ const correctCp = lineEvalCpForGap(evalAfterCorrect.lines[0]);
3535
+ if (wrongCp === null || correctCp === null) {
3536
+ return null;
3537
+ }
3538
+ return wrongCp - correctCp;
3539
+ }
3540
+ function refutationFromEvaluation(fenAfterWrong, evaluation, evalGapCp, evalGapApplies, evalGapLoading) {
3541
+ var _a, _b, _c, _d, _e, _f, _g;
3542
+ const loading = evaluation.status === 'loading' ||
3543
+ evaluation.status === 'analyzing' ||
3544
+ evalGapLoading;
3545
+ if (evaluation.status === 'error') {
3546
+ return {
3547
+ refutationUci: null,
3548
+ refutationSan: null,
3549
+ refutationLine: null,
3550
+ loading: false,
3551
+ error: (_a = evaluation.error) !== null && _a !== void 0 ? _a : 'Engine unavailable',
3552
+ };
3553
+ }
3554
+ const meetsThreshold = !evalGapApplies ||
3555
+ (evalGapCp !== null && evalGapCp >= REFUTATION_EVAL_GAP_CP);
3556
+ if (!meetsThreshold) {
3557
+ return {
3558
+ refutationUci: null,
3559
+ refutationSan: null,
3560
+ refutationLine: null,
3561
+ loading,
3562
+ error: null,
3563
+ };
3564
+ }
3565
+ const refutationUci = (_d = (_c = (_b = evaluation.lines[0]) === null || _b === void 0 ? void 0 : _b.pv) === null || _c === void 0 ? void 0 : _c[0]) !== null && _d !== void 0 ? _d : null;
3566
+ const refutationSan = refutationUci
3567
+ ? ((_e = uciPvToSan(fenAfterWrong, [refutationUci])[0]) !== null && _e !== void 0 ? _e : refutationUci)
3568
+ : null;
3569
+ const refutationLine = ((_g = (_f = evaluation.lines[0]) === null || _f === void 0 ? void 0 : _f.pv) === null || _g === void 0 ? void 0 : _g.length)
3570
+ ? formatPvPreview(fenAfterWrong, evaluation.lines[0].pv, 4)
3571
+ : null;
3572
+ return {
3573
+ refutationUci,
3574
+ refutationSan,
3575
+ refutationLine,
3576
+ loading,
3577
+ error: null,
3578
+ };
3579
+ }
3580
+
3581
+ const MISS_WRONG_PAUSE_MS = 450;
3582
+ const MISS_REFUTATION_PAUSE_MS = 900;
3583
+ const MISS_REFUTATION_MAX_WAIT_MS = 4000;
3584
+ const MISS_MOVE_ANIMATION_MS = 220;
3585
+ function moveArrow(uci, color) {
3586
+ if (!uci || uci.length < 4) {
3587
+ return [];
3588
+ }
3589
+ return [[uci.slice(0, 2), uci.slice(2, 4), color]];
3590
+ }
3591
+ function expectedMoveArrow(expectedUci, color) {
3592
+ return moveArrow(expectedUci, color);
3593
+ }
3594
+ function getMissDisplay(sequence, expectedUci, refutationUci, answerArrowColor) {
3595
+ var _a;
3596
+ if (!sequence) {
3597
+ return {
3598
+ fen: null,
3599
+ arrows: [],
3600
+ animating: false,
3601
+ };
3602
+ }
3603
+ const { setupFen, attemptedUci, phase } = sequence;
3604
+ const fenAfterWrong = fenAfterUci(setupFen, attemptedUci);
3605
+ switch (phase) {
3606
+ case 'wrong':
3607
+ return {
3608
+ fen: fenAfterWrong !== null && fenAfterWrong !== void 0 ? fenAfterWrong : setupFen,
3609
+ arrows: [],
3610
+ animating: false,
3611
+ };
3612
+ case 'refutation': {
3613
+ const fenAfterRefutation = fenAfterWrong && refutationUci
3614
+ ? fenAfterUci(fenAfterWrong, refutationUci)
3615
+ : null;
3616
+ return {
3617
+ fen: (_a = fenAfterRefutation !== null && fenAfterRefutation !== void 0 ? fenAfterRefutation : fenAfterWrong) !== null && _a !== void 0 ? _a : setupFen,
3618
+ arrows: [],
3619
+ animating: Boolean(fenAfterRefutation),
3620
+ };
3621
+ }
3622
+ case 'retry':
3623
+ return {
3624
+ fen: setupFen,
3625
+ arrows: [],
3626
+ animating: false,
3627
+ };
3628
+ case 'answer':
3629
+ return {
3630
+ fen: setupFen,
3631
+ arrows: expectedMoveArrow(expectedUci, answerArrowColor),
3632
+ animating: false,
3633
+ };
3634
+ default:
3635
+ return {
3636
+ fen: setupFen,
3637
+ arrows: [],
3638
+ animating: false,
3639
+ };
3640
+ }
3641
+ }
3642
+
3643
+ function useMissRefutation(setupFen, attemptedUci, expectedUci, enabled, engineOptions) {
3644
+ const fenAfterWrong = react.useMemo(() => {
3645
+ if (!setupFen || !attemptedUci) {
3646
+ return null;
3647
+ }
3648
+ return fenAfterUci(setupFen, attemptedUci);
3649
+ }, [setupFen, attemptedUci]);
3650
+ const fenAfterCorrect = react.useMemo(() => {
3651
+ if (!setupFen || !expectedUci) {
3652
+ return null;
3653
+ }
3654
+ return fenAfterUci(setupFen, expectedUci);
3655
+ }, [setupFen, expectedUci]);
3656
+ const wrongEvaluation = useAnalysisEngine(fenAfterWrong !== null && fenAfterWrong !== void 0 ? fenAfterWrong : '', Object.assign(Object.assign({}, engineOptions), { enabled: enabled && Boolean(fenAfterWrong), shared: false }));
3657
+ const correctEvaluation = useAnalysisEngine(fenAfterCorrect !== null && fenAfterCorrect !== void 0 ? fenAfterCorrect : '', Object.assign(Object.assign({}, engineOptions), { enabled: enabled && Boolean(fenAfterCorrect), shared: false }));
3658
+ return react.useMemo(() => {
3659
+ if (!fenAfterWrong) {
3660
+ return {
3661
+ fenAfterWrong: null,
3662
+ refutationUci: null,
3663
+ refutationSan: null,
3664
+ refutationLine: null,
3665
+ loading: false,
3666
+ error: null,
3667
+ };
3668
+ }
3669
+ const evalGapApplies = Boolean(fenAfterCorrect);
3670
+ const evalGapCp = evalGapApplies
3671
+ ? refutationEvalGapCp(wrongEvaluation, correctEvaluation)
3672
+ : null;
3673
+ const evalGapLoading = evalGapApplies &&
3674
+ evalGapCp === null &&
3675
+ wrongEvaluation.status !== 'error' &&
3676
+ correctEvaluation.status !== 'error' &&
3677
+ (correctEvaluation.status === 'loading' ||
3678
+ correctEvaluation.status === 'analyzing' ||
3679
+ wrongEvaluation.status === 'loading' ||
3680
+ wrongEvaluation.status === 'analyzing');
3681
+ return Object.assign({ fenAfterWrong }, refutationFromEvaluation(fenAfterWrong, wrongEvaluation, evalGapCp, evalGapApplies, evalGapLoading));
3682
+ }, [fenAfterCorrect, fenAfterWrong, correctEvaluation, wrongEvaluation]);
3683
+ }
3684
+
3685
+ function useMissSequence(feedback, expectedUci, engineOptions, answerArrowColor, autoShowWrongMoves) {
3686
+ var _a, _b;
3687
+ const [sequence, setSequence] = react.useState(null);
3688
+ const refutation = useMissRefutation((_a = sequence === null || sequence === void 0 ? void 0 : sequence.setupFen) !== null && _a !== void 0 ? _a : null, (_b = sequence === null || sequence === void 0 ? void 0 : sequence.attemptedUci) !== null && _b !== void 0 ? _b : null, expectedUci, sequence != null, engineOptions);
3689
+ const startSequence = react.useCallback((setupFen, attemptedUci) => {
3690
+ setSequence({
3691
+ setupFen,
3692
+ attemptedUci,
3693
+ phase: autoShowWrongMoves ? 'wrong' : 'retry',
3694
+ });
3695
+ }, [autoShowWrongMoves]);
3696
+ const clearSequence = react.useCallback(() => {
3697
+ setSequence(null);
3698
+ }, []);
3699
+ const prevFeedbackRef = react.useRef(feedback);
3700
+ react.useEffect(() => {
3701
+ const prevFeedback = prevFeedbackRef.current;
3702
+ prevFeedbackRef.current = feedback;
3703
+ if (prevFeedback === 'incorrect' && feedback !== 'incorrect') {
3704
+ setSequence(null);
3705
+ }
3706
+ }, [feedback]);
3707
+ react.useEffect(() => {
3708
+ if (!sequence || sequence.phase !== 'wrong' || !autoShowWrongMoves) {
3709
+ return undefined;
3710
+ }
3711
+ if (refutation.loading) {
3712
+ const maxWait = window.setTimeout(() => {
3713
+ setSequence((current) => (current === null || current === void 0 ? void 0 : current.phase) === 'wrong' ? Object.assign(Object.assign({}, current), { phase: 'answer' }) : current);
3714
+ }, MISS_REFUTATION_MAX_WAIT_MS);
3715
+ return () => window.clearTimeout(maxWait);
3716
+ }
3717
+ const delay = window.setTimeout(() => {
3718
+ setSequence((current) => {
3719
+ if (!current || current.phase !== 'wrong') {
3720
+ return current;
3721
+ }
3722
+ return Object.assign(Object.assign({}, current), { phase: refutation.refutationUci ? 'refutation' : 'answer' });
3723
+ });
3724
+ }, MISS_WRONG_PAUSE_MS);
3725
+ return () => window.clearTimeout(delay);
3726
+ }, [
3727
+ autoShowWrongMoves,
3728
+ refutation.loading,
3729
+ refutation.refutationUci,
3730
+ sequence,
3731
+ ]);
3732
+ react.useEffect(() => {
3733
+ if (!sequence || sequence.phase !== 'refutation') {
3734
+ return undefined;
3735
+ }
3736
+ const delay = window.setTimeout(() => {
3737
+ setSequence((current) => (current === null || current === void 0 ? void 0 : current.phase) === 'refutation'
3738
+ ? Object.assign(Object.assign({}, current), { phase: 'answer' }) : current);
3739
+ }, MISS_REFUTATION_PAUSE_MS);
3740
+ return () => window.clearTimeout(delay);
3741
+ }, [sequence]);
3742
+ const display = react.useMemo(() => getMissDisplay(sequence, expectedUci, refutation.refutationUci, answerArrowColor), [
3743
+ answerArrowColor,
3744
+ expectedUci,
3745
+ refutation.refutationUci,
3746
+ sequence,
3747
+ ]);
3748
+ return {
3749
+ sequence,
3750
+ refutation,
3751
+ display,
3752
+ startSequence,
3753
+ clearSequence,
3754
+ };
3755
+ }
3756
+
3757
+ function useMissBoard({ feedback, expectedUci, positionFen, answerArrowColor, autoShowWrongMoves = true, engineOptions, }) {
3758
+ var _a;
3759
+ const refutationEngine = react.useMemo(() => (Object.assign(Object.assign({}, refutationEngineOptions), engineOptions)), [engineOptions]);
3760
+ const missSequence = useMissSequence(feedback, expectedUci, refutationEngine, answerArrowColor, autoShowWrongMoves);
3761
+ const customArrows = react.useMemo(() => {
3762
+ if (feedback !== 'incorrect') {
3763
+ return [];
3764
+ }
3765
+ if (missSequence.sequence) {
3766
+ return missSequence.display.arrows;
3767
+ }
3768
+ if (expectedUci) {
3769
+ return [
3770
+ [
3771
+ expectedUci.slice(0, 2),
3772
+ expectedUci.slice(2, 4),
3773
+ answerArrowColor,
3774
+ ],
3775
+ ];
3776
+ }
3777
+ return [];
3778
+ }, [
3779
+ answerArrowColor,
3780
+ expectedUci,
3781
+ feedback,
3782
+ missSequence.display.arrows,
3783
+ missSequence.sequence,
3784
+ ]);
3785
+ const boardPosition = (_a = missSequence.display.fen) !== null && _a !== void 0 ? _a : positionFen;
3786
+ const wrapDropHandler = react.useCallback((onDrop, { enabled, dropFen = boardPosition, expectedMoveUci = expectedUci, }) => (source, target, piece) => {
3787
+ if (enabled && expectedMoveUci) {
3788
+ const uci = uciFromDrop(dropFen, source, target, piece);
3789
+ if (uci && uci.toLowerCase() !== expectedMoveUci.toLowerCase()) {
3790
+ missSequence.startSequence(dropFen, uci);
3791
+ }
3792
+ else if (uci &&
3793
+ uci.toLowerCase() === expectedMoveUci.toLowerCase()) {
3794
+ missSequence.clearSequence();
3795
+ }
3796
+ }
3797
+ return onDrop(source, target, piece);
3798
+ }, [
3799
+ boardPosition,
3800
+ expectedUci,
3801
+ missSequence.clearSequence,
3802
+ missSequence.startSequence,
3803
+ ]);
3804
+ return {
3805
+ missSequence,
3806
+ refutation: missSequence.refutation,
3807
+ customArrows,
3808
+ boardPosition,
3809
+ boardAnimating: missSequence.display.animating,
3810
+ wrapDropHandler,
3811
+ };
3812
+ }
3813
+
1497
3814
  exports.AnalysisBoard = AnalysisBoard;
1498
3815
  exports.AnalysisBoardCore = AnalysisBoardCore;
1499
3816
  exports.AnalysisBoardCoreView = AnalysisBoardCoreView;
1500
3817
  exports.AnalysisBoardLayout = AnalysisBoardLayout;
1501
3818
  exports.AnalysisChessboardView = AnalysisChessboardView;
3819
+ exports.AnalysisEngineProvider = AnalysisEngineProvider;
1502
3820
  exports.AnalysisErrorBoundary = AnalysisErrorBoundary;
1503
3821
  exports.AnalysisPosition = AnalysisPosition;
3822
+ exports.BOARD_THEMES = BOARD_THEMES;
3823
+ exports.BOARD_THEME_IDS = BOARD_THEME_IDS;
3824
+ exports.ChessboardDnDProvider = ChessboardDnDProvider;
1504
3825
  exports.ChessboardThemeContext = ChessboardThemeContext;
1505
3826
  exports.DEFAULT_ANALYSIS_LAYOUT = DEFAULT_ANALYSIS_LAYOUT;
3827
+ exports.DEFAULT_BOARD_THEME = DEFAULT_BOARD_THEME;
1506
3828
  exports.DEFAULT_STOCKFISH_SCRIPT_URL = DEFAULT_STOCKFISH_SCRIPT_URL;
1507
3829
  exports.DefaultAnalysisContainer = DefaultAnalysisContainer;
1508
3830
  exports.DefaultAnalysisSidebar = DefaultAnalysisSidebar;
1509
3831
  exports.DefaultPlyNavigation = DefaultPlyNavigation;
1510
3832
  exports.EngineEvaluationPanel = EngineEvaluationPanel;
1511
3833
  exports.HighlightChessboard = HighlightChessboard;
3834
+ exports.MISS_MOVE_ANIMATION_MS = MISS_MOVE_ANIMATION_MS;
3835
+ exports.MISS_REFUTATION_MAX_WAIT_MS = MISS_REFUTATION_MAX_WAIT_MS;
3836
+ exports.MISS_REFUTATION_PAUSE_MS = MISS_REFUTATION_PAUSE_MS;
3837
+ exports.MISS_WRONG_PAUSE_MS = MISS_WRONG_PAUSE_MS;
1512
3838
  exports.PlyNavigation = PlyNavigation;
3839
+ exports.REFUTATION_EVAL_GAP_CP = REFUTATION_EVAL_GAP_CP;
3840
+ exports.REFUTATION_EVAL_GAP_PAWNS = REFUTATION_EVAL_GAP_PAWNS;
1513
3841
  exports.StockfishBrowserEngine = StockfishBrowserEngine;
1514
3842
  exports.ThemeProvider = ThemeProvider;
1515
3843
  exports.analysisBoardHighlightColors = analysisBoardHighlightColors;
1516
3844
  exports.analysisSidebarColors = analysisSidebarColors;
1517
3845
  exports.applyUciMove = applyUciMove;
1518
3846
  exports.boardSquareHighlightColors = boardSquareHighlightColors;
3847
+ exports.boardThemeFromLegacyUiTheme = boardThemeFromLegacyUiTheme;
3848
+ exports.createExpectedMoveDropHandler = createExpectedMoveDropHandler;
1519
3849
  exports.createSidebarRowBandCounters = createSidebarRowBandCounters;
1520
3850
  exports.defaultRenderPlyNavigation = defaultRenderPlyNavigation;
1521
3851
  exports.emptyEngineEvaluation = emptyEngineEvaluation;
3852
+ exports.evaluateExpectedMoveDrop = evaluateExpectedMoveDrop;
3853
+ exports.fenAfterUci = fenAfterUci;
1522
3854
  exports.formatEvaluation = formatEvaluation;
1523
3855
  exports.formatPvPreview = formatPvPreview;
1524
3856
  exports.getAnalysisModalStyles = getAnalysisModalStyles;
3857
+ exports.getBoardThemeStyles = getBoardThemeStyles;
1525
3858
  exports.getCheckSquareFromChess = getCheckSquareFromChess;
1526
3859
  exports.getLastMoveSquareStyles = getLastMoveSquareStyles;
3860
+ exports.getMissDisplay = getMissDisplay;
1527
3861
  exports.getSidebarRowBackground = getSidebarRowBackground;
1528
3862
  exports.getStylesForTheme = getStylesForTheme;
1529
3863
  exports.isAnalyzableFen = isAnalyzableFen;
3864
+ exports.isBoardThemeId = isBoardThemeId;
3865
+ exports.isEditableKeyboardTarget = isEditableKeyboardTarget;
3866
+ exports.lineEvalCpForGap = lineEvalCpForGap;
3867
+ exports.matchesExpectedUci = matchesExpectedUci;
1530
3868
  exports.navButtonStyle = navButtonStyle;
1531
3869
  exports.navRowStyle = navRowStyle;
1532
3870
  exports.normalizeEvalForWhite = normalizeEvalForWhite;
1533
3871
  exports.normalizePvMoves = normalizePvMoves;
3872
+ exports.normalizeSubscriberOptions = normalizeSubscriberOptions;
1534
3873
  exports.parseUciInfoLine = parseUciInfoLine;
1535
3874
  exports.parseUciMove = parseUciMove;
1536
3875
  exports.plyLabelStyle = plyLabelStyle;
1537
3876
  exports.plyNavigationPalette = palette;
3877
+ exports.refutationEngineOptions = refutationEngineOptions;
3878
+ exports.refutationEvalGapCp = refutationEvalGapCp;
3879
+ exports.refutationFromEvaluation = refutationFromEvaluation;
1538
3880
  exports.resolveStockfishScriptUrl = resolveStockfishScriptUrl;
1539
3881
  exports.resolveStockfishWasmUrl = resolveStockfishWasmUrl;
1540
3882
  exports.resolveStockfishWorkerUrl = resolveStockfishWorkerUrl;
1541
3883
  exports.scrubberInputStyle = scrubberInputStyle;
1542
3884
  exports.splitWorkerLines = splitWorkerLines;
3885
+ exports.uciFromDrop = uciFromDrop;
1543
3886
  exports.uciPvToSan = uciPvToSan;
1544
3887
  exports.useAnalysisBoardModel = useAnalysisBoardModel;
1545
3888
  exports.useAnalysisEngine = useAnalysisEngine;
3889
+ exports.useAnalysisEngineContext = useAnalysisEngineContext;
3890
+ exports.useBoardRevision = useBoardRevision;
1546
3891
  exports.useChessboardTheme = useChessboardTheme;
3892
+ exports.useMissBoard = useMissBoard;
3893
+ exports.useMissRefutation = useMissRefutation;
3894
+ exports.useMissSequence = useMissSequence;
3895
+ exports.usePositionKeyboardNav = usePositionKeyboardNav;
1547
3896
  exports.useTheme = useTheme;