@dxos/react-ui-gameboard 0.8.4-main.67995b8 → 0.8.4-main.72ec0f3

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 (67) hide show
  1. package/dist/lib/browser/index.mjs +407 -337
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +407 -337
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Chessboard/Chessboard.d.ts +20 -0
  8. package/dist/types/src/components/Chessboard/Chessboard.d.ts.map +1 -0
  9. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts +30 -0
  10. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts.map +1 -0
  11. package/dist/types/src/components/Chessboard/chess.d.ts +59 -0
  12. package/dist/types/src/components/Chessboard/chess.d.ts.map +1 -0
  13. package/dist/types/src/components/Chessboard/chess.test.d.ts +2 -0
  14. package/dist/types/src/components/Chessboard/chess.test.d.ts.map +1 -0
  15. package/dist/types/src/components/Chessboard/index.d.ts.map +1 -0
  16. package/dist/types/src/components/Gameboard/Gameboard.d.ts +38 -0
  17. package/dist/types/src/components/Gameboard/Gameboard.d.ts.map +1 -0
  18. package/dist/types/src/{Gameboard → components/Gameboard}/Piece.d.ts +3 -2
  19. package/dist/types/src/components/Gameboard/Piece.d.ts.map +1 -0
  20. package/dist/types/src/components/Gameboard/Square.d.ts.map +1 -0
  21. package/dist/types/src/components/Gameboard/index.d.ts +4 -0
  22. package/dist/types/src/components/Gameboard/index.d.ts.map +1 -0
  23. package/dist/types/src/{Gameboard → components/Gameboard}/types.d.ts +2 -0
  24. package/dist/types/src/components/Gameboard/types.d.ts.map +1 -0
  25. package/dist/types/src/components/Gameboard/util.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +3 -0
  27. package/dist/types/src/components/index.d.ts.map +1 -0
  28. package/dist/types/src/index.d.ts +1 -2
  29. package/dist/types/src/index.d.ts.map +1 -1
  30. package/dist/types/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +23 -22
  32. package/src/{Chessboard → components/Chessboard}/Chessboard.stories.tsx +27 -28
  33. package/src/{Chessboard → components/Chessboard}/Chessboard.tsx +58 -54
  34. package/src/components/Chessboard/chess.test.ts +19 -0
  35. package/src/components/Chessboard/chess.ts +322 -0
  36. package/src/components/Gameboard/Gameboard.tsx +140 -0
  37. package/src/{Gameboard → components/Gameboard}/Piece.tsx +24 -22
  38. package/src/{Gameboard → components/Gameboard}/Square.tsx +4 -4
  39. package/src/{Gameboard → components/Gameboard}/index.ts +0 -3
  40. package/src/{Gameboard → components/Gameboard}/types.ts +3 -0
  41. package/src/components/index.ts +6 -0
  42. package/src/index.ts +1 -2
  43. package/dist/types/src/Chessboard/Chessboard.d.ts +0 -15
  44. package/dist/types/src/Chessboard/Chessboard.d.ts.map +0 -1
  45. package/dist/types/src/Chessboard/Chessboard.stories.d.ts +0 -16
  46. package/dist/types/src/Chessboard/Chessboard.stories.d.ts.map +0 -1
  47. package/dist/types/src/Chessboard/chess.d.ts +0 -40
  48. package/dist/types/src/Chessboard/chess.d.ts.map +0 -1
  49. package/dist/types/src/Chessboard/index.d.ts.map +0 -1
  50. package/dist/types/src/Gameboard/Gameboard.d.ts +0 -23
  51. package/dist/types/src/Gameboard/Gameboard.d.ts.map +0 -1
  52. package/dist/types/src/Gameboard/Piece.d.ts.map +0 -1
  53. package/dist/types/src/Gameboard/Square.d.ts.map +0 -1
  54. package/dist/types/src/Gameboard/context.d.ts +0 -10
  55. package/dist/types/src/Gameboard/context.d.ts.map +0 -1
  56. package/dist/types/src/Gameboard/index.d.ts +0 -7
  57. package/dist/types/src/Gameboard/index.d.ts.map +0 -1
  58. package/dist/types/src/Gameboard/types.d.ts.map +0 -1
  59. package/dist/types/src/Gameboard/util.d.ts.map +0 -1
  60. package/src/Chessboard/chess.ts +0 -213
  61. package/src/Gameboard/Gameboard.tsx +0 -103
  62. package/src/Gameboard/context.ts +0 -22
  63. /package/dist/types/src/{Chessboard → components/Chessboard}/index.d.ts +0 -0
  64. /package/dist/types/src/{Gameboard → components/Gameboard}/Square.d.ts +0 -0
  65. /package/dist/types/src/{Gameboard → components/Gameboard}/util.d.ts +0 -0
  66. /package/src/{Chessboard → components/Chessboard}/index.ts +0 -0
  67. /package/src/{Gameboard → components/Gameboard}/util.ts +0 -0
@@ -5,22 +5,14 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
- // src/Gameboard/context.ts
9
- import { createContext, useContext } from "react";
10
- import { raise } from "@dxos/debug";
11
- var GameboardContext = createContext(void 0);
12
- var useBoardContext = () => {
13
- return useContext(GameboardContext) ?? raise(new Error("Missing BoardContext"));
14
- };
15
-
16
- // src/Gameboard/types.ts
8
+ // src/components/Gameboard/types.ts
17
9
  var locationToString = (location) => location.join("-");
18
10
  var stringToLocation = (str) => str.split("-").map(Number);
19
11
  var isPiece = (piece) => piece != null && typeof piece === "object" && "id" in piece && "type" in piece && "location" in piece && isLocation(piece.location);
20
12
  var isLocation = (token) => Array.isArray(token) && token.length === 2 && token.every((val) => typeof val === "number");
21
13
  var isEqualLocation = (l1, l2) => l1[0] === l2[0] && l1[1] === l2[1];
22
14
 
23
- // src/Gameboard/util.ts
15
+ // src/components/Gameboard/util.ts
24
16
  var getRelativeBounds = (container, element) => {
25
17
  const containerRect = container.getBoundingClientRect();
26
18
  const elementRect = element.getBoundingClientRect();
@@ -32,133 +24,165 @@ var getRelativeBounds = (container, element) => {
32
24
  };
33
25
  };
34
26
 
35
- // src/Gameboard/Gameboard.tsx
36
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
27
+ // src/components/Gameboard/Gameboard.tsx
28
+ import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
37
29
  import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
38
- import React, { forwardRef, useCallback, useEffect, useState } from "react";
30
+ import { createContext } from "@radix-ui/react-context";
31
+ import React3, { forwardRef, useCallback, useEffect as useEffect3, useState as useState3 } from "react";
32
+ import { log as log3 } from "@dxos/log";
33
+ import { mx as mx3 } from "@dxos/react-ui-theme";
34
+
35
+ // src/components/Gameboard/Piece.tsx
36
+ import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
37
+ import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
38
+ import { centerUnderPointer } from "@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer";
39
+ import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
40
+ import React, { memo, useEffect, useRef, useState } from "react";
41
+ import { createPortal } from "react-dom";
42
+ import { invariant } from "@dxos/invariant";
39
43
  import { log } from "@dxos/log";
44
+ import { useDynamicRef } from "@dxos/react-ui";
40
45
  import { mx } from "@dxos/react-ui-theme";
41
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/Gameboard/Gameboard.tsx";
42
- var GameboardRoot = ({ children, model, onDrop }) => {
46
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Gameboard/Piece.tsx";
47
+ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, bounds, label, onClick }) => {
43
48
  var _effect = _useSignals();
44
49
  try {
50
+ const { model, dragging: isDragging, promoting } = useGameboardContext(Piece.displayName);
51
+ const promotingRef = useDynamicRef(promoting);
45
52
  const [dragging, setDragging] = useState(false);
46
- const [promoting, setPromoting] = useState();
47
- const onPromotion = useCallback((move) => {
48
- log("onPromotion", {
49
- move
50
- }, {
51
- F: __dxlog_file,
52
- L: 29,
53
- S: void 0,
54
- C: (f, a) => f(...a)
55
- });
56
- setPromoting(void 0);
57
- onDrop?.(move);
58
- }, []);
53
+ const [preview, setPreview] = useState();
54
+ const [current, setCurrent] = useState({});
55
+ const ref = useRef(null);
59
56
  useEffect(() => {
60
57
  if (!model) {
61
58
  return;
62
59
  }
63
- return monitorForElements({
64
- onDragStart: ({ source }) => {
65
- log("onDragStart", {
66
- source
60
+ const el = ref.current;
61
+ invariant(el, void 0, {
62
+ F: __dxlog_file,
63
+ L: 45,
64
+ S: void 0,
65
+ A: [
66
+ "el",
67
+ ""
68
+ ]
69
+ });
70
+ return draggable({
71
+ element: el,
72
+ getInitialData: () => ({
73
+ piece
74
+ }),
75
+ onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
76
+ log("onGenerateDragPreview", {
77
+ source: source.data
67
78
  }, {
68
79
  F: __dxlog_file,
69
- L: 42,
80
+ L: 51,
70
81
  S: void 0,
71
82
  C: (f, a) => f(...a)
72
83
  });
73
- setDragging(true);
74
- },
75
- onDrop: ({ source, location }) => {
76
- log("onDrop", {
77
- source,
78
- location
79
- }, {
80
- F: __dxlog_file,
81
- L: 46,
82
- S: void 0,
83
- C: (f, a) => f(...a)
84
+ setCustomNativeDragPreview({
85
+ getOffset: centerUnderPointer,
86
+ render: ({ container }) => {
87
+ setPreview(container);
88
+ const { width, height } = el.getBoundingClientRect();
89
+ container.style.width = width + "px";
90
+ container.style.height = height + "px";
91
+ return () => {
92
+ setPreview(void 0);
93
+ };
94
+ },
95
+ nativeSetDragImage
84
96
  });
85
- const target = location.current.dropTargets[0];
86
- if (!target) {
87
- return;
88
- }
89
- const targetLocation = target.data.location;
90
- const piece = source.data.piece;
91
- if (!isLocation(targetLocation) || !isPiece(piece)) {
92
- return;
93
- }
94
- const move = {
95
- from: piece.location,
96
- to: targetLocation,
97
- piece: piece.type
98
- };
99
- if (model.canPromote?.(move)) {
100
- setPromoting({
101
- ...piece,
102
- location: targetLocation
103
- });
104
- } else {
105
- onDrop?.(move);
97
+ },
98
+ canDrag: () => !promotingRef.current && !model.readonly && model.turn === piece.side,
99
+ onDragStart: () => setDragging(true),
100
+ onDrop: ({ location: { current: current2 } }) => {
101
+ try {
102
+ const location = current2.dropTargets[0]?.data.location;
103
+ if (isLocation(location)) {
104
+ setCurrent((current3) => ({
105
+ ...current3,
106
+ location
107
+ }));
108
+ }
109
+ } catch {
106
110
  }
107
111
  setDragging(false);
108
112
  }
109
113
  });
110
114
  }, [
111
- model
115
+ model,
116
+ piece
112
117
  ]);
113
- return /* @__PURE__ */ React.createElement(GameboardContext.Provider, {
114
- value: {
115
- model,
116
- dragging,
117
- promoting,
118
- onPromotion
119
- }
120
- }, children);
121
- } finally {
122
- _effect.f();
123
- }
124
- };
125
- GameboardRoot.displayName = "Gameboard.Root";
126
- var GameboardContent = /* @__PURE__ */ forwardRef(({ children, classNames, style }, forwardedRef) => {
127
- var _effect = _useSignals();
128
- try {
129
- return /* @__PURE__ */ React.createElement("div", {
130
- ref: forwardedRef,
131
- style,
132
- className: "flex w-full h-full justify-center overflow-hidden"
133
- }, /* @__PURE__ */ React.createElement("div", {
134
- className: mx("max-w-full max-h-full content-center aspect-square", classNames)
135
- }, children));
118
+ useEffect(() => {
119
+ requestAnimationFrame(() => {
120
+ if (!ref.current || !bounds) {
121
+ return;
122
+ }
123
+ if (!current.location || !isEqualLocation(current.location, piece.location)) {
124
+ ref.current.style.transition = "top 250ms ease-out, left 250ms ease-out";
125
+ ref.current.style.top = bounds.top + "px";
126
+ ref.current.style.left = bounds.left + "px";
127
+ setCurrent({
128
+ location: piece.location,
129
+ bounds
130
+ });
131
+ } else if (current.bounds !== bounds) {
132
+ ref.current.style.transition = "none";
133
+ ref.current.style.top = bounds.top + "px";
134
+ ref.current.style.left = bounds.left + "px";
135
+ setCurrent({
136
+ location: piece.location,
137
+ bounds
138
+ });
139
+ }
140
+ });
141
+ }, [
142
+ current,
143
+ piece.location,
144
+ bounds
145
+ ]);
146
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
147
+ ref,
148
+ style: {
149
+ width: bounds?.width,
150
+ height: bounds?.height
151
+ },
152
+ className: mx("absolute", classNames, dragging && "opacity-20", isDragging && "pointer-events-none"),
153
+ onClick
154
+ }, /* @__PURE__ */ React.createElement(Component, {
155
+ className: "grow"
156
+ }), label && /* @__PURE__ */ React.createElement("div", {
157
+ className: "absolute inset-1 text-xs text-black"
158
+ }, label)), preview && /* @__PURE__ */ createPortal(/* @__PURE__ */ React.createElement("div", {
159
+ className: mx(classNames)
160
+ }, /* @__PURE__ */ React.createElement(Component, {
161
+ className: "grow"
162
+ })), preview));
136
163
  } finally {
137
164
  _effect.f();
138
165
  }
139
166
  });
140
- var Gameboard = {
141
- Root: GameboardRoot,
142
- Content: GameboardContent
143
- };
167
+ Piece.displayName = "Piece";
144
168
 
145
- // src/Gameboard/Square.tsx
169
+ // src/components/Gameboard/Square.tsx
146
170
  import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
147
171
  import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
148
- import React2, { useRef, useState as useState2, useEffect as useEffect2, memo } from "react";
149
- import { invariant } from "@dxos/invariant";
172
+ import React2, { memo as memo2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
173
+ import { invariant as invariant2 } from "@dxos/invariant";
150
174
  import { log as log2 } from "@dxos/log";
151
175
  import { mx as mx2 } from "@dxos/react-ui-theme";
152
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/Gameboard/Square.tsx";
153
- var Square = /* @__PURE__ */ memo(({ location, bounds, label, classNames }) => {
176
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Gameboard/Square.tsx";
177
+ var Square = /* @__PURE__ */ memo2(({ location, bounds, label, classNames }) => {
154
178
  var _effect = _useSignals2();
155
179
  try {
156
- const ref = useRef(null);
180
+ const ref = useRef2(null);
157
181
  const [state, setState] = useState2("idle");
158
- const { model } = useBoardContext();
182
+ const { model } = useGameboardContext(Square.displayName);
159
183
  useEffect2(() => {
160
184
  const el = ref.current;
161
- invariant(el, void 0, {
185
+ invariant2(el, void 0, {
162
186
  F: __dxlog_file2,
163
187
  L: 32,
164
188
  S: void 0,
@@ -226,155 +250,122 @@ var Square = /* @__PURE__ */ memo(({ location, bounds, label, classNames }) => {
226
250
  });
227
251
  Square.displayName = "Square";
228
252
 
229
- // src/Gameboard/Piece.tsx
230
- import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
231
- import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
232
- import { centerUnderPointer } from "@atlaskit/pragmatic-drag-and-drop/element/center-under-pointer";
233
- import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview";
234
- import React3, { useState as useState3, useRef as useRef2, useEffect as useEffect3, memo as memo2 } from "react";
235
- import { createPortal } from "react-dom";
236
- import { invariant as invariant2 } from "@dxos/invariant";
237
- import { log as log3 } from "@dxos/log";
238
- import { useDynamicRef, useTrackProps } from "@dxos/react-ui";
239
- import { mx as mx3 } from "@dxos/react-ui-theme";
240
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/Gameboard/Piece.tsx";
241
- var Piece = /* @__PURE__ */ memo2(({ classNames, piece, orientation, bounds, label, Component }) => {
253
+ // src/components/Gameboard/Gameboard.tsx
254
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Gameboard/Gameboard.tsx";
255
+ var [GameboardContextProvider, useRadixGameboardContext] = createContext("Gameboard");
256
+ var useGameboardContext = (consumerName) => {
257
+ return useRadixGameboardContext(consumerName);
258
+ };
259
+ var GameboardRoot = ({ children, model, moveNumber, onDrop }) => {
242
260
  var _effect = _useSignals3();
243
261
  try {
244
- useTrackProps({
245
- classNames,
246
- piece,
247
- orientation,
248
- bounds,
249
- label,
250
- Component
251
- }, Piece.displayName, false);
252
- const { model } = useBoardContext();
253
- const { dragging: isDragging, promoting } = useBoardContext();
254
- const promotingRef = useDynamicRef(promoting);
255
262
  const [dragging, setDragging] = useState3(false);
256
- const [preview, setPreview] = useState3();
257
- const [current, setCurrent] = useState3({});
258
- const ref = useRef2(null);
259
- useEffect3(() => {
260
- const el = ref.current;
261
- invariant2(el, void 0, {
263
+ const [promoting, setPromoting] = useState3();
264
+ const handlePromotion = useCallback((move) => {
265
+ log3("onPromotion", {
266
+ move
267
+ }, {
262
268
  F: __dxlog_file3,
263
- L: 44,
269
+ L: 48,
264
270
  S: void 0,
265
- A: [
266
- "el",
267
- ""
268
- ]
271
+ C: (f, a) => f(...a)
269
272
  });
270
- return draggable({
271
- element: el,
272
- getInitialData: () => ({
273
- piece
274
- }),
275
- onGenerateDragPreview: ({ nativeSetDragImage, location, source }) => {
276
- log3("onGenerateDragPreview", {
277
- source: source.data
273
+ setPromoting(void 0);
274
+ onDrop?.(move);
275
+ }, []);
276
+ useEffect3(() => {
277
+ if (!model) {
278
+ return;
279
+ }
280
+ return monitorForElements({
281
+ onDragStart: ({ source }) => {
282
+ log3("onDragStart", {
283
+ source
278
284
  }, {
279
285
  F: __dxlog_file3,
280
- L: 50,
286
+ L: 61,
281
287
  S: void 0,
282
288
  C: (f, a) => f(...a)
283
289
  });
284
- setCustomNativeDragPreview({
285
- getOffset: centerUnderPointer,
286
- // getOffset: preserveOffsetOnSource({
287
- // element: source.element,
288
- // input: location.current.input,
289
- // }),
290
- render: ({ container }) => {
291
- setPreview(container);
292
- const { width, height } = el.getBoundingClientRect();
293
- container.style.width = width + "px";
294
- container.style.height = height + "px";
295
- return () => {
296
- setPreview(void 0);
297
- };
298
- },
299
- nativeSetDragImage
300
- });
290
+ setDragging(true);
301
291
  },
302
- canDrag: () => !promotingRef.current && model?.turn === piece.side,
303
- onDragStart: () => setDragging(true),
304
- onDrop: ({ location: { current: current2 } }) => {
305
- const location = current2.dropTargets[0].data.location;
306
- if (isLocation(location)) {
307
- setCurrent((current3) => ({
308
- ...current3,
309
- location
310
- }));
292
+ onDrop: ({ source, location }) => {
293
+ log3("onDrop", {
294
+ source,
295
+ location
296
+ }, {
297
+ F: __dxlog_file3,
298
+ L: 65,
299
+ S: void 0,
300
+ C: (f, a) => f(...a)
301
+ });
302
+ const target = location.current.dropTargets[0];
303
+ if (!target) {
304
+ return;
305
+ }
306
+ const targetLocation = target.data.location;
307
+ const piece = source.data.piece;
308
+ if (!isLocation(targetLocation) || !isPiece(piece)) {
309
+ return;
310
+ }
311
+ const move = {
312
+ from: piece.location,
313
+ to: targetLocation,
314
+ piece: piece.type
315
+ };
316
+ if (model.isValidMove(move)) {
317
+ if (model.canPromote?.(move)) {
318
+ setPromoting({
319
+ ...piece,
320
+ location: targetLocation
321
+ });
322
+ } else {
323
+ onDrop?.(move);
324
+ }
311
325
  }
312
326
  setDragging(false);
313
327
  }
314
328
  });
315
329
  }, [
316
- model,
317
- piece
318
- ]);
319
- useEffect3(() => {
320
- requestAnimationFrame(() => {
321
- if (!ref.current || !bounds) {
322
- return;
323
- }
324
- if (!current.location || !isEqualLocation(current.location, piece.location)) {
325
- ref.current.style.transition = "top 400ms ease-out, left 400ms ease-out";
326
- ref.current.style.top = bounds.top + "px";
327
- ref.current.style.left = bounds.left + "px";
328
- setCurrent({
329
- location: piece.location,
330
- bounds
331
- });
332
- } else if (current.bounds !== bounds) {
333
- ref.current.style.transition = "none";
334
- ref.current.style.top = bounds.top + "px";
335
- ref.current.style.left = bounds.left + "px";
336
- setCurrent({
337
- location: piece.location,
338
- bounds
339
- });
340
- }
341
- });
342
- }, [
343
- current,
344
- piece.location,
345
- bounds
330
+ model
346
331
  ]);
347
- return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("div", {
348
- ref,
349
- style: {
350
- width: bounds?.width,
351
- height: bounds?.height
352
- },
353
- className: mx3(
354
- "absolute",
355
- classNames,
356
- // orientation === 'black' && '_rotate-180',
357
- dragging && "opacity-20",
358
- isDragging && "pointer-events-none"
359
- )
360
- }, /* @__PURE__ */ React3.createElement(Component, {
361
- className: "grow"
362
- }), label && /* @__PURE__ */ React3.createElement("div", {
363
- className: "absolute inset-1 text-xs text-black"
364
- }, label)), preview && /* @__PURE__ */ createPortal(/* @__PURE__ */ React3.createElement("div", {
365
- className: mx3(classNames)
366
- }, /* @__PURE__ */ React3.createElement(Component, {
367
- className: "grow"
368
- })), preview));
332
+ return /* @__PURE__ */ React3.createElement(GameboardContextProvider, {
333
+ model,
334
+ dragging,
335
+ promoting,
336
+ onPromotion: handlePromotion
337
+ }, children);
338
+ } finally {
339
+ _effect.f();
340
+ }
341
+ };
342
+ GameboardRoot.displayName = "Gameboard.Root";
343
+ var GameboardContent = /* @__PURE__ */ forwardRef(({ children, classNames, grow, contain }, forwardedRef) => {
344
+ var _effect = _useSignals3();
345
+ try {
346
+ return /* @__PURE__ */ React3.createElement("div", {
347
+ role: "none",
348
+ className: mx3(grow && "grid is-full bs-full size-container place-content-center", classNames),
349
+ ref: forwardedRef
350
+ }, contain ? /* @__PURE__ */ React3.createElement("div", {
351
+ className: "is-[min(100cqw,100cqh)] bs-[min(100cqw,100cqh)]"
352
+ }, children) : children);
369
353
  } finally {
370
354
  _effect.f();
371
355
  }
372
356
  });
373
- Piece.displayName = "Piece";
357
+ GameboardContent.displayName = "Gameboard.Content";
358
+ var Gameboard = {
359
+ Root: GameboardRoot,
360
+ Content: GameboardContent,
361
+ Piece,
362
+ Square
363
+ };
374
364
 
375
- // src/Chessboard/chess.ts
365
+ // src/components/Chessboard/chess.ts
376
366
  import { signal } from "@preact/signals-core";
377
- import { Chess, validateFen } from "chess.js";
367
+ import { Chess as ChessJS } from "chess.js";
368
+ import { invariant as invariant3 } from "@dxos/invariant";
378
369
  import { log as log4 } from "@dxos/log";
379
370
 
380
371
  // src/gen/pieces/chess/alpha/index.ts
@@ -667,8 +658,8 @@ var SvgWR = (props) => {
667
658
  };
668
659
  var wR_default = SvgWR;
669
660
 
670
- // src/Chessboard/chess.ts
671
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/Chessboard/chess.ts";
661
+ // src/components/Chessboard/chess.ts
662
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Chessboard/chess.ts";
672
663
  var ChessPieces = alpha_exports;
673
664
  var posToLocation = (pos) => {
674
665
  const col = pos.charCodeAt(0) - "a".charCodeAt(0);
@@ -681,6 +672,9 @@ var posToLocation = (pos) => {
681
672
  var locationToPos = ([row, col]) => {
682
673
  return String.fromCharCode(col + "a".charCodeAt(0)) + (row + 1);
683
674
  };
675
+ var getRawPgn = (pgn) => {
676
+ return pgn.replace(/\[.*?\]/g, "").trim();
677
+ };
684
678
  var styles = {
685
679
  neutral: {
686
680
  black: "bg-neutral-50",
@@ -707,55 +701,88 @@ var boardStyles = styles.original;
707
701
  var getSquareColor = ([row, col]) => {
708
702
  return (col + row) % 2 === 0 ? boardStyles.black : boardStyles.white;
709
703
  };
710
- var makeMove = (game, move) => {
711
- const from = locationToPos(move.from);
712
- const to = locationToPos(move.to);
713
- try {
714
- log4("makeMove", {
715
- move
716
- }, {
717
- F: __dxlog_file4,
718
- L: 72,
719
- S: void 0,
720
- C: (f, a) => f(...a)
721
- });
722
- const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
723
- game.move({
724
- from,
725
- to,
726
- promotion
727
- }, {
728
- strict: false
729
- });
730
- return game;
731
- } catch (err) {
732
- return null;
704
+ var createChess = (pgn) => {
705
+ const chess = new ChessJS();
706
+ if (pgn) {
707
+ try {
708
+ chess.loadPgn(pgn);
709
+ } catch {
710
+ log4.warn(pgn, void 0, {
711
+ F: __dxlog_file4,
712
+ L: 76,
713
+ S: void 0,
714
+ C: (f, a) => f(...a)
715
+ });
716
+ }
733
717
  }
718
+ return chess;
734
719
  };
735
720
  var ChessModel = class {
736
- constructor(fen) {
737
- this._pieces = signal({});
738
- this.initialize(fen);
721
+ _chess = new ChessJS();
722
+ _pieces = signal({});
723
+ _moveIndex = signal(0);
724
+ constructor(pgn) {
725
+ this.update(pgn);
726
+ }
727
+ get readonly() {
728
+ return this._moveIndex.value !== this._chess.history().length;
739
729
  }
740
730
  get turn() {
741
- return this._game.turn() === "w" ? "white" : "black";
731
+ return this._chess.turn() === "w" ? "white" : "black";
732
+ }
733
+ get game() {
734
+ return this._chess;
742
735
  }
743
736
  get pieces() {
744
737
  return this._pieces;
745
738
  }
746
- get game() {
747
- return this._game;
739
+ get moveIndex() {
740
+ return this._moveIndex;
748
741
  }
749
742
  get fen() {
750
- return this._game.fen();
743
+ return this._chess.fen();
744
+ }
745
+ /**
746
+ * PGN with headers.
747
+ *
748
+ * [Event "?"]
749
+ * [Site "?"]
750
+ * [Date "2025.08.05"]
751
+ * [Round "?"]
752
+ * [White "?"]
753
+ * [Black "?"]
754
+ * [Result "*"]
755
+ */
756
+ // TODO(burdon): Update headers.
757
+ get pgn() {
758
+ return getRawPgn(this._chess.pgn());
751
759
  }
752
- initialize(fen) {
753
- this._pieces.value = {};
754
- this._game = new Chess(fen ? validateFen(fen).ok ? fen : void 0 : void 0);
755
- this._update();
760
+ setMoveIndex(index) {
761
+ const temp = new ChessJS();
762
+ const history = this._chess.history({
763
+ verbose: true
764
+ });
765
+ for (let i = 0; i < index && i < history.length; i++) {
766
+ temp.move(history[i]);
767
+ }
768
+ this._updateBoard(temp);
769
+ }
770
+ update(pgn = "") {
771
+ const previous = this._chess.history();
772
+ try {
773
+ this._chess.loadPgn(pgn);
774
+ this._chess.setHeader("Date", createDate());
775
+ this._chess.setHeader("Site", "dxos.org");
776
+ } catch {
777
+ }
778
+ const current = this._chess.history();
779
+ if (!isValidNextMove(previous, current)) {
780
+ this._pieces.value = {};
781
+ }
782
+ this._updateBoard(this._chess);
756
783
  }
757
784
  isValidMove(move) {
758
- return makeMove(new Chess(this._game.fen()), move) !== null;
785
+ return tryMove(new ChessJS(this._chess.fen()), move) !== null;
759
786
  }
760
787
  canPromote(move) {
761
788
  const isPawnMove = move.piece === "BP" || move.piece === "WP";
@@ -763,47 +790,104 @@ var ChessModel = class {
763
790
  return isPawnMove && isToLastRank;
764
791
  }
765
792
  makeMove(move) {
766
- const game = makeMove(this._game, move);
793
+ const game = tryMove(this._chess, move);
767
794
  if (!game) {
768
795
  return false;
769
796
  }
770
- this._game = game;
771
- this._update();
797
+ this._updateBoard(this._chess);
772
798
  return true;
773
799
  }
774
800
  makeRandomMove() {
775
- const moves = this._game.moves();
801
+ const moves = this._chess.moves();
776
802
  if (!moves.length) {
777
803
  return false;
778
804
  }
779
805
  const move = moves[Math.floor(Math.random() * moves.length)];
780
- this._game.move(move);
781
- this._update();
806
+ this._chess.move(move);
807
+ this._updateBoard(this._chess);
782
808
  return true;
783
809
  }
784
810
  /**
785
811
  * Update pieces preserving identity.
786
812
  */
787
- _update() {
788
- const pieces = {};
789
- this._game.board().flatMap((row) => row.forEach((record) => {
790
- if (!record) {
791
- return;
792
- }
793
- const { square, type, color } = record;
794
- const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
795
- const location = posToLocation(square);
796
- pieces[locationToString(location)] = {
797
- id: `${square}-${pieceType}`,
798
- type: pieceType,
799
- side: color === "w" ? "white" : "black",
800
- location
801
- };
802
- }));
803
- this._pieces.value = mapPieces(this._pieces.value, pieces);
813
+ _updateBoard(chess) {
814
+ this._pieces.value = createPieceMap(chess);
815
+ this._moveIndex.value = chess.history().length;
804
816
  }
805
817
  };
806
- var mapPieces = (before, after) => {
818
+ var tryMove = (chess, move) => {
819
+ const from = locationToPos(move.from);
820
+ const to = locationToPos(move.to);
821
+ try {
822
+ const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
823
+ chess.move({
824
+ from,
825
+ to,
826
+ promotion
827
+ }, {
828
+ strict: false
829
+ });
830
+ return chess;
831
+ } catch {
832
+ return null;
833
+ }
834
+ };
835
+ var isValidNextMove = (previous, current) => {
836
+ if (current.length > previous.length + 1) {
837
+ return false;
838
+ }
839
+ for (let i = 0; i < previous.length; i++) {
840
+ if (previous[i] !== current[i]) {
841
+ return false;
842
+ }
843
+ }
844
+ return true;
845
+ };
846
+ var createPieceMap = (chess) => {
847
+ const temp = new ChessJS();
848
+ let pieces = _createPieceMap(temp);
849
+ const history = chess.history({
850
+ verbose: true
851
+ });
852
+ for (let i = 0; i < history.length; i++) {
853
+ const move = history[i];
854
+ temp.move(move);
855
+ pieces = _diffPieces(pieces, _createPieceMap(temp));
856
+ const test = /* @__PURE__ */ new Set();
857
+ Object.values(pieces).forEach((piece) => {
858
+ invariant3(!test.has(piece.id), "Duplicate: " + piece.id, {
859
+ F: __dxlog_file4,
860
+ L: 252,
861
+ S: void 0,
862
+ A: [
863
+ "!test.has(piece.id)",
864
+ "'Duplicate: ' + piece.id"
865
+ ]
866
+ });
867
+ test.add(piece.id);
868
+ });
869
+ }
870
+ return pieces;
871
+ };
872
+ var _createPieceMap = (chess) => {
873
+ const pieces = {};
874
+ chess.board().flatMap((row) => row.forEach((record) => {
875
+ if (!record) {
876
+ return;
877
+ }
878
+ const { square, type, color } = record;
879
+ const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
880
+ const location = posToLocation(square);
881
+ pieces[locationToString(location)] = {
882
+ id: `${square}-${pieceType}`,
883
+ type: pieceType,
884
+ side: color === "w" ? "white" : "black",
885
+ location
886
+ };
887
+ }));
888
+ return pieces;
889
+ };
890
+ var _diffPieces = (before, after) => {
807
891
  const difference = {
808
892
  removed: {},
809
893
  added: {}
@@ -826,40 +910,27 @@ var mapPieces = (before, after) => {
826
910
  piece.id = previous.id;
827
911
  }
828
912
  }
829
- log4("delta", {
830
- before: Object.keys(before).length,
831
- after: Object.keys(after).length,
832
- removed: Object.keys(difference.removed).length,
833
- added: Object.keys(difference.added).length
834
- }, {
835
- F: __dxlog_file4,
836
- L: 205,
837
- S: void 0,
838
- C: (f, a) => f(...a)
839
- });
840
913
  return after;
841
914
  };
915
+ var createDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10).replace(/-/g, ".");
842
916
 
843
- // src/Chessboard/Chessboard.tsx
917
+ // src/components/Chessboard/Chessboard.tsx
844
918
  import { useSignals as _useSignals16 } from "@preact-signals/safe-react/tracking";
845
- import React16, { useRef as useRef3, useMemo, useEffect as useEffect4, useState as useState4, memo as memo3 } from "react";
919
+ import React16, { forwardRef as forwardRef2, memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
846
920
  import { useResizeDetector } from "react-resize-detector";
847
- import { useTrackProps as useTrackProps2 } from "@dxos/react-ui";
921
+ import { useForwardedRef } from "@dxos/react-ui";
848
922
  import { mx as mx4 } from "@dxos/react-ui-theme";
849
- import { isNotFalsy } from "@dxos/util";
850
- var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows = 8, cols = 8, classNames }) => {
923
+ import { isNonNullable } from "@dxos/util";
924
+ var ChessboardComponent = /* @__PURE__ */ forwardRef2(({ classNames, orientation, showLabels, debug, rows = 8, cols = 8 }, forwardedRef) => {
851
925
  var _effect = _useSignals16();
852
926
  try {
853
- useTrackProps2({
854
- orientation,
855
- showLabels,
856
- debug
857
- }, Chessboard.displayName, false);
858
- const { ref: containerRef, width, height } = useResizeDetector({
927
+ const targetRef = useForwardedRef(forwardedRef);
928
+ const { width, height } = useResizeDetector({
929
+ targetRef,
859
930
  refreshRate: 200
860
931
  });
861
- const { model, promoting, onPromotion } = useBoardContext();
862
- const locations = useMemo(() => {
932
+ const { model, promoting, onPromotion } = useGameboardContext(Chessboard.displayName);
933
+ const squares = useMemo(() => {
863
934
  return Array.from({
864
935
  length: rows
865
936
  }, (_, i) => orientation === "black" ? i : rows - 1 - i).flatMap((row) => Array.from({
@@ -874,19 +945,19 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
874
945
  cols
875
946
  ]);
876
947
  const layout = useMemo(() => {
877
- return locations.map((location) => {
948
+ return squares.map((location) => {
878
949
  return /* @__PURE__ */ React16.createElement("div", {
879
950
  key: locationToString(location),
880
951
  ["data-location"]: locationToString(location)
881
952
  });
882
953
  });
883
954
  }, [
884
- locations
955
+ squares
885
956
  ]);
886
957
  const [grid, setGrid] = useState4({});
887
958
  const gridRef = useRef3(null);
888
959
  useEffect4(() => {
889
- setGrid(locations.reduce((acc, location) => {
960
+ setGrid(squares.reduce((acc, location) => {
890
961
  const square = getSquareLocation(gridRef.current, location);
891
962
  const bounds = getRelativeBounds(gridRef.current, square);
892
963
  return {
@@ -895,7 +966,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
895
966
  };
896
967
  }, {}));
897
968
  }, [
898
- locations,
969
+ squares,
899
970
  width,
900
971
  height
901
972
  ]);
@@ -912,19 +983,20 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
912
983
  piece,
913
984
  bounds
914
985
  };
915
- }).filter(isNotFalsy);
986
+ }).filter(isNonNullable);
916
987
  }, [
917
988
  grid,
918
989
  model?.pieces.value,
919
990
  promoting
920
991
  ]);
921
992
  return /* @__PURE__ */ React16.createElement("div", {
922
- ref: containerRef,
923
- className: mx4("relative", classNames)
993
+ ref: targetRef,
994
+ tabIndex: 0,
995
+ className: mx4("relative outline-none", classNames)
924
996
  }, /* @__PURE__ */ React16.createElement("div", {
925
997
  ref: gridRef,
926
998
  className: "grid grid-rows-8 grid-cols-8 aspect-square select-none"
927
- }, layout), /* @__PURE__ */ React16.createElement("div", null, locations.map((location) => /* @__PURE__ */ React16.createElement(Square, {
999
+ }, layout), /* @__PURE__ */ React16.createElement("div", null, squares.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
928
1000
  key: locationToString(location),
929
1001
  location,
930
1002
  label: showLabels ? locationToPos(location) : void 0,
@@ -932,14 +1004,14 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
932
1004
  classNames: getSquareColor(location)
933
1005
  }))), /* @__PURE__ */ React16.createElement("div", {
934
1006
  className: mx4(promoting && "opacity-50")
935
- }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Piece, {
1007
+ }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
936
1008
  key: piece.id,
937
1009
  piece,
938
1010
  bounds,
939
1011
  label: debug ? piece.id : void 0,
940
1012
  orientation,
941
1013
  Component: ChessPieces[piece.type]
942
- }))), /* @__PURE__ */ React16.createElement("div", null, promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
1014
+ }))), promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
943
1015
  grid,
944
1016
  piece: promoting,
945
1017
  onSelect: (piece) => {
@@ -950,15 +1022,13 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
950
1022
  promotion: piece.type
951
1023
  });
952
1024
  }
953
- })));
1025
+ }));
954
1026
  } finally {
955
1027
  _effect.f();
956
1028
  }
957
1029
  });
958
- Chessboard.displayName = "Chessboard";
959
- var getSquareLocation = (container, location) => {
960
- return container.querySelector(`[data-location="${locationToString(location)}"]`);
961
- };
1030
+ ChessboardComponent.displayName = "Chessboard";
1031
+ var Chessboard = /* @__PURE__ */ memo3(ChessboardComponent);
962
1032
  var PromotionSelector = ({ grid, piece, onSelect }) => {
963
1033
  var _effect = _useSignals16();
964
1034
  try {
@@ -988,29 +1058,30 @@ var PromotionSelector = ({ grid, piece, onSelect }) => {
988
1058
  type: selected.type
989
1059
  });
990
1060
  };
991
- return /* @__PURE__ */ React16.createElement("div", null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement("div", {
1061
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
992
1062
  key: piece2.id,
993
- style: bounds,
994
- onClick: () => handleSelect(piece2)
995
- }, /* @__PURE__ */ React16.createElement(Piece, {
1063
+ classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
996
1064
  piece: piece2,
997
1065
  bounds,
998
1066
  Component: ChessPieces[piece2.type],
999
- classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion)
1000
- }))));
1067
+ onClick: () => handleSelect(piece2)
1068
+ })));
1001
1069
  } finally {
1002
1070
  _effect.f();
1003
1071
  }
1004
1072
  };
1073
+ var getSquareLocation = (container, location) => {
1074
+ return container.querySelector(`[data-location="${locationToString(location)}"]`);
1075
+ };
1005
1076
  export {
1006
1077
  ChessModel,
1007
1078
  ChessPieces,
1008
1079
  Chessboard,
1009
1080
  Gameboard,
1010
- GameboardContext,
1011
- Piece,
1012
- Square,
1013
1081
  boardStyles,
1082
+ createChess,
1083
+ createPieceMap,
1084
+ getRawPgn,
1014
1085
  getRelativeBounds,
1015
1086
  getSquareColor,
1016
1087
  isEqualLocation,
@@ -1018,9 +1089,8 @@ export {
1018
1089
  isPiece,
1019
1090
  locationToPos,
1020
1091
  locationToString,
1021
- mapPieces,
1022
1092
  posToLocation,
1023
1093
  stringToLocation,
1024
- useBoardContext
1094
+ useGameboardContext
1025
1095
  };
1026
1096
  //# sourceMappingURL=index.mjs.map