@dxos/react-ui-gameboard 0.8.4-main.b97322e → 0.8.4-main.dedc0f3

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 +426 -330
  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 +426 -330
  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 +31 -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 +15 -13
  32. package/src/{Chessboard → components/Chessboard}/Chessboard.stories.tsx +24 -23
  33. package/src/{Chessboard → components/Chessboard}/Chessboard.tsx +55 -50
  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 +25 -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,173 @@ 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, useTrackProps } 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, orientation, bounds, label, onClick }) => {
43
48
  var _effect = _useSignals();
44
49
  try {
50
+ useTrackProps({
51
+ classNames,
52
+ Component,
53
+ piece,
54
+ orientation,
55
+ bounds,
56
+ label
57
+ }, Piece.displayName, false);
58
+ const { model, dragging: isDragging, promoting } = useGameboardContext(Piece.displayName);
59
+ const promotingRef = useDynamicRef(promoting);
45
60
  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
- }, []);
61
+ const [preview, setPreview] = useState();
62
+ const [current, setCurrent] = useState({});
63
+ const ref = useRef(null);
59
64
  useEffect(() => {
60
65
  if (!model) {
61
66
  return;
62
67
  }
63
- return monitorForElements({
64
- onDragStart: ({ source }) => {
65
- log("onDragStart", {
66
- source
68
+ const el = ref.current;
69
+ invariant(el, void 0, {
70
+ F: __dxlog_file,
71
+ L: 46,
72
+ S: void 0,
73
+ A: [
74
+ "el",
75
+ ""
76
+ ]
77
+ });
78
+ return draggable({
79
+ element: el,
80
+ getInitialData: () => ({
81
+ piece
82
+ }),
83
+ onGenerateDragPreview: ({ nativeSetDragImage, source }) => {
84
+ log("onGenerateDragPreview", {
85
+ source: source.data
67
86
  }, {
68
87
  F: __dxlog_file,
69
- L: 42,
88
+ L: 52,
70
89
  S: void 0,
71
90
  C: (f, a) => f(...a)
72
91
  });
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)
92
+ setCustomNativeDragPreview({
93
+ getOffset: centerUnderPointer,
94
+ render: ({ container }) => {
95
+ setPreview(container);
96
+ const { width, height } = el.getBoundingClientRect();
97
+ container.style.width = width + "px";
98
+ container.style.height = height + "px";
99
+ return () => {
100
+ setPreview(void 0);
101
+ };
102
+ },
103
+ nativeSetDragImage
84
104
  });
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);
105
+ },
106
+ canDrag: () => !promotingRef.current && !model.readonly && model.turn === piece.side,
107
+ onDragStart: () => setDragging(true),
108
+ onDrop: ({ location: { current: current2 } }) => {
109
+ try {
110
+ const location = current2.dropTargets[0]?.data.location;
111
+ if (isLocation(location)) {
112
+ setCurrent((current3) => ({
113
+ ...current3,
114
+ location
115
+ }));
116
+ }
117
+ } catch {
106
118
  }
107
119
  setDragging(false);
108
120
  }
109
121
  });
110
122
  }, [
111
- model
123
+ model,
124
+ piece
112
125
  ]);
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));
126
+ useEffect(() => {
127
+ requestAnimationFrame(() => {
128
+ if (!ref.current || !bounds) {
129
+ return;
130
+ }
131
+ if (!current.location || !isEqualLocation(current.location, piece.location)) {
132
+ ref.current.style.transition = "top 250ms ease-out, left 250ms ease-out";
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
+ } else if (current.bounds !== bounds) {
140
+ ref.current.style.transition = "none";
141
+ ref.current.style.top = bounds.top + "px";
142
+ ref.current.style.left = bounds.left + "px";
143
+ setCurrent({
144
+ location: piece.location,
145
+ bounds
146
+ });
147
+ }
148
+ });
149
+ }, [
150
+ current,
151
+ piece.location,
152
+ bounds
153
+ ]);
154
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
155
+ ref,
156
+ style: {
157
+ width: bounds?.width,
158
+ height: bounds?.height
159
+ },
160
+ className: mx("absolute", classNames, dragging && "opacity-20", isDragging && "pointer-events-none"),
161
+ onClick
162
+ }, /* @__PURE__ */ React.createElement(Component, {
163
+ className: "grow"
164
+ }), label && /* @__PURE__ */ React.createElement("div", {
165
+ className: "absolute inset-1 text-xs text-black"
166
+ }, label)), preview && /* @__PURE__ */ createPortal(/* @__PURE__ */ React.createElement("div", {
167
+ className: mx(classNames)
168
+ }, /* @__PURE__ */ React.createElement(Component, {
169
+ className: "grow"
170
+ })), preview));
136
171
  } finally {
137
172
  _effect.f();
138
173
  }
139
174
  });
140
- var Gameboard = {
141
- Root: GameboardRoot,
142
- Content: GameboardContent
143
- };
175
+ Piece.displayName = "Piece";
144
176
 
145
- // src/Gameboard/Square.tsx
177
+ // src/components/Gameboard/Square.tsx
146
178
  import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
147
179
  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";
180
+ import React2, { memo as memo2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
181
+ import { invariant as invariant2 } from "@dxos/invariant";
150
182
  import { log as log2 } from "@dxos/log";
151
183
  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 }) => {
184
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Gameboard/Square.tsx";
185
+ var Square = /* @__PURE__ */ memo2(({ location, bounds, label, classNames }) => {
154
186
  var _effect = _useSignals2();
155
187
  try {
156
- const ref = useRef(null);
188
+ const ref = useRef2(null);
157
189
  const [state, setState] = useState2("idle");
158
- const { model } = useBoardContext();
190
+ const { model } = useGameboardContext(Square.displayName);
159
191
  useEffect2(() => {
160
192
  const el = ref.current;
161
- invariant(el, void 0, {
193
+ invariant2(el, void 0, {
162
194
  F: __dxlog_file2,
163
195
  L: 32,
164
196
  S: void 0,
@@ -226,155 +258,122 @@ var Square = /* @__PURE__ */ memo(({ location, bounds, label, classNames }) => {
226
258
  });
227
259
  Square.displayName = "Square";
228
260
 
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 }) => {
261
+ // src/components/Gameboard/Gameboard.tsx
262
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Gameboard/Gameboard.tsx";
263
+ var [GameboardContextProvider, useRadixGameboardContext] = createContext("Gameboard");
264
+ var useGameboardContext = (consumerName) => {
265
+ return useRadixGameboardContext(consumerName);
266
+ };
267
+ var GameboardRoot = ({ children, model, moveNumber, onDrop }) => {
242
268
  var _effect = _useSignals3();
243
269
  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
270
  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, {
271
+ const [promoting, setPromoting] = useState3();
272
+ const handlePromotion = useCallback((move) => {
273
+ log3("onPromotion", {
274
+ move
275
+ }, {
262
276
  F: __dxlog_file3,
263
- L: 44,
277
+ L: 48,
264
278
  S: void 0,
265
- A: [
266
- "el",
267
- ""
268
- ]
279
+ C: (f, a) => f(...a)
269
280
  });
270
- return draggable({
271
- element: el,
272
- getInitialData: () => ({
273
- piece
274
- }),
275
- onGenerateDragPreview: ({ nativeSetDragImage, location, source }) => {
276
- log3("onGenerateDragPreview", {
277
- source: source.data
281
+ setPromoting(void 0);
282
+ onDrop?.(move);
283
+ }, []);
284
+ useEffect3(() => {
285
+ if (!model) {
286
+ return;
287
+ }
288
+ return monitorForElements({
289
+ onDragStart: ({ source }) => {
290
+ log3("onDragStart", {
291
+ source
278
292
  }, {
279
293
  F: __dxlog_file3,
280
- L: 50,
294
+ L: 61,
281
295
  S: void 0,
282
296
  C: (f, a) => f(...a)
283
297
  });
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
- });
298
+ setDragging(true);
301
299
  },
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
- }));
300
+ onDrop: ({ source, location }) => {
301
+ log3("onDrop", {
302
+ source,
303
+ location
304
+ }, {
305
+ F: __dxlog_file3,
306
+ L: 65,
307
+ S: void 0,
308
+ C: (f, a) => f(...a)
309
+ });
310
+ const target = location.current.dropTargets[0];
311
+ if (!target) {
312
+ return;
313
+ }
314
+ const targetLocation = target.data.location;
315
+ const piece = source.data.piece;
316
+ if (!isLocation(targetLocation) || !isPiece(piece)) {
317
+ return;
318
+ }
319
+ const move = {
320
+ from: piece.location,
321
+ to: targetLocation,
322
+ piece: piece.type
323
+ };
324
+ if (model.isValidMove(move)) {
325
+ if (model.canPromote?.(move)) {
326
+ setPromoting({
327
+ ...piece,
328
+ location: targetLocation
329
+ });
330
+ } else {
331
+ onDrop?.(move);
332
+ }
311
333
  }
312
334
  setDragging(false);
313
335
  }
314
336
  });
315
337
  }, [
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
338
+ model
346
339
  ]);
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));
340
+ return /* @__PURE__ */ React3.createElement(GameboardContextProvider, {
341
+ model,
342
+ dragging,
343
+ promoting,
344
+ onPromotion: handlePromotion
345
+ }, children);
346
+ } finally {
347
+ _effect.f();
348
+ }
349
+ };
350
+ GameboardRoot.displayName = "Gameboard.Root";
351
+ var GameboardContent = /* @__PURE__ */ forwardRef(({ children, classNames, grow, contain }, forwardedRef) => {
352
+ var _effect = _useSignals3();
353
+ try {
354
+ return /* @__PURE__ */ React3.createElement("div", {
355
+ role: "none",
356
+ className: mx3(grow && "grid is-full bs-full size-container place-content-center", classNames),
357
+ ref: forwardedRef
358
+ }, contain ? /* @__PURE__ */ React3.createElement("div", {
359
+ className: "is-[min(100cqw,100cqh)] bs-[min(100cqw,100cqh)]"
360
+ }, children) : children);
369
361
  } finally {
370
362
  _effect.f();
371
363
  }
372
364
  });
373
- Piece.displayName = "Piece";
365
+ GameboardContent.displayName = "Gameboard.Content";
366
+ var Gameboard = {
367
+ Root: GameboardRoot,
368
+ Content: GameboardContent,
369
+ Piece,
370
+ Square
371
+ };
374
372
 
375
- // src/Chessboard/chess.ts
373
+ // src/components/Chessboard/chess.ts
376
374
  import { signal } from "@preact/signals-core";
377
- import { Chess, validateFen } from "chess.js";
375
+ import { Chess as ChessJS } from "chess.js";
376
+ import { invariant as invariant3 } from "@dxos/invariant";
378
377
  import { log as log4 } from "@dxos/log";
379
378
 
380
379
  // src/gen/pieces/chess/alpha/index.ts
@@ -667,8 +666,21 @@ var SvgWR = (props) => {
667
666
  };
668
667
  var wR_default = SvgWR;
669
668
 
670
- // src/Chessboard/chess.ts
671
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/Chessboard/chess.ts";
669
+ // src/components/Chessboard/chess.ts
670
+ function _define_property(obj, key, value) {
671
+ if (key in obj) {
672
+ Object.defineProperty(obj, key, {
673
+ value,
674
+ enumerable: true,
675
+ configurable: true,
676
+ writable: true
677
+ });
678
+ } else {
679
+ obj[key] = value;
680
+ }
681
+ return obj;
682
+ }
683
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Chessboard/chess.ts";
672
684
  var ChessPieces = alpha_exports;
673
685
  var posToLocation = (pos) => {
674
686
  const col = pos.charCodeAt(0) - "a".charCodeAt(0);
@@ -681,6 +693,9 @@ var posToLocation = (pos) => {
681
693
  var locationToPos = ([row, col]) => {
682
694
  return String.fromCharCode(col + "a".charCodeAt(0)) + (row + 1);
683
695
  };
696
+ var getRawPgn = (pgn) => {
697
+ return pgn.replace(/\[.*?\]/g, "").trim();
698
+ };
684
699
  var styles = {
685
700
  neutral: {
686
701
  black: "bg-neutral-50",
@@ -707,55 +722,82 @@ var boardStyles = styles.original;
707
722
  var getSquareColor = ([row, col]) => {
708
723
  return (col + row) % 2 === 0 ? boardStyles.black : boardStyles.white;
709
724
  };
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;
725
+ var createChess = (pgn) => {
726
+ const chess = new ChessJS();
727
+ if (pgn) {
728
+ try {
729
+ chess.loadPgn(pgn);
730
+ } catch {
731
+ log4.warn(pgn, void 0, {
732
+ F: __dxlog_file4,
733
+ L: 76,
734
+ S: void 0,
735
+ C: (f, a) => f(...a)
736
+ });
737
+ }
733
738
  }
739
+ return chess;
734
740
  };
735
741
  var ChessModel = class {
736
- constructor(fen) {
737
- this._pieces = signal({});
738
- this.initialize(fen);
742
+ get readonly() {
743
+ return this._moveIndex.value !== this._chess.history().length;
739
744
  }
740
745
  get turn() {
741
- return this._game.turn() === "w" ? "white" : "black";
746
+ return this._chess.turn() === "w" ? "white" : "black";
747
+ }
748
+ get game() {
749
+ return this._chess;
742
750
  }
743
751
  get pieces() {
744
752
  return this._pieces;
745
753
  }
746
- get game() {
747
- return this._game;
754
+ get moveIndex() {
755
+ return this._moveIndex;
748
756
  }
749
757
  get fen() {
750
- return this._game.fen();
758
+ return this._chess.fen();
759
+ }
760
+ /**
761
+ * PGN with headers.
762
+ *
763
+ * [Event "?"]
764
+ * [Site "?"]
765
+ * [Date "2025.08.05"]
766
+ * [Round "?"]
767
+ * [White "?"]
768
+ * [Black "?"]
769
+ * [Result "*"]
770
+ */
771
+ // TODO(burdon): Update headers.
772
+ get pgn() {
773
+ return getRawPgn(this._chess.pgn());
751
774
  }
752
- initialize(fen) {
753
- this._pieces.value = {};
754
- this._game = new Chess(fen ? validateFen(fen).ok ? fen : void 0 : void 0);
755
- this._update();
775
+ setMoveIndex(index) {
776
+ const temp = new ChessJS();
777
+ const history = this._chess.history({
778
+ verbose: true
779
+ });
780
+ for (let i = 0; i < index && i < history.length; i++) {
781
+ temp.move(history[i]);
782
+ }
783
+ this._updateBoard(temp);
784
+ }
785
+ update(pgn = "") {
786
+ const previous = this._chess.history();
787
+ try {
788
+ this._chess.loadPgn(pgn);
789
+ this._chess.setHeader("Date", createDate());
790
+ this._chess.setHeader("Site", "dxos.org");
791
+ } catch {
792
+ }
793
+ const current = this._chess.history();
794
+ if (!isValidNextMove(previous, current)) {
795
+ this._pieces.value = {};
796
+ }
797
+ this._updateBoard(this._chess);
756
798
  }
757
799
  isValidMove(move) {
758
- return makeMove(new Chess(this._game.fen()), move) !== null;
800
+ return tryMove(new ChessJS(this._chess.fen()), move) !== null;
759
801
  }
760
802
  canPromote(move) {
761
803
  const isPawnMove = move.piece === "BP" || move.piece === "WP";
@@ -763,47 +805,110 @@ var ChessModel = class {
763
805
  return isPawnMove && isToLastRank;
764
806
  }
765
807
  makeMove(move) {
766
- const game = makeMove(this._game, move);
808
+ const game = tryMove(this._chess, move);
767
809
  if (!game) {
768
810
  return false;
769
811
  }
770
- this._game = game;
771
- this._update();
812
+ this._updateBoard(this._chess);
772
813
  return true;
773
814
  }
774
815
  makeRandomMove() {
775
- const moves = this._game.moves();
816
+ const moves = this._chess.moves();
776
817
  if (!moves.length) {
777
818
  return false;
778
819
  }
779
820
  const move = moves[Math.floor(Math.random() * moves.length)];
780
- this._game.move(move);
781
- this._update();
821
+ this._chess.move(move);
822
+ this._updateBoard(this._chess);
782
823
  return true;
783
824
  }
784
825
  /**
785
826
  * Update pieces preserving identity.
786
827
  */
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);
828
+ _updateBoard(chess) {
829
+ this._pieces.value = createPieceMap(chess);
830
+ this._moveIndex.value = chess.history().length;
831
+ }
832
+ constructor(pgn) {
833
+ _define_property(this, "_chess", new ChessJS());
834
+ _define_property(this, "_pieces", signal({}));
835
+ _define_property(this, "_moveIndex", signal(0));
836
+ this.update(pgn);
804
837
  }
805
838
  };
806
- var mapPieces = (before, after) => {
839
+ var tryMove = (chess, move) => {
840
+ const from = locationToPos(move.from);
841
+ const to = locationToPos(move.to);
842
+ try {
843
+ const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
844
+ chess.move({
845
+ from,
846
+ to,
847
+ promotion
848
+ }, {
849
+ strict: false
850
+ });
851
+ return chess;
852
+ } catch {
853
+ return null;
854
+ }
855
+ };
856
+ var isValidNextMove = (previous, current) => {
857
+ if (current.length > previous.length + 1) {
858
+ return false;
859
+ }
860
+ for (let i = 0; i < previous.length; i++) {
861
+ if (previous[i] !== current[i]) {
862
+ return false;
863
+ }
864
+ }
865
+ return true;
866
+ };
867
+ var createPieceMap = (chess) => {
868
+ const temp = new ChessJS();
869
+ let pieces = _createPieceMap(temp);
870
+ const history = chess.history({
871
+ verbose: true
872
+ });
873
+ for (let i = 0; i < history.length; i++) {
874
+ const move = history[i];
875
+ temp.move(move);
876
+ pieces = _diffPieces(pieces, _createPieceMap(temp));
877
+ const test = /* @__PURE__ */ new Set();
878
+ Object.values(pieces).forEach((piece) => {
879
+ invariant3(!test.has(piece.id), "Duplicate: " + piece.id, {
880
+ F: __dxlog_file4,
881
+ L: 252,
882
+ S: void 0,
883
+ A: [
884
+ "!test.has(piece.id)",
885
+ "'Duplicate: ' + piece.id"
886
+ ]
887
+ });
888
+ test.add(piece.id);
889
+ });
890
+ }
891
+ return pieces;
892
+ };
893
+ var _createPieceMap = (chess) => {
894
+ const pieces = {};
895
+ chess.board().flatMap((row) => row.forEach((record) => {
896
+ if (!record) {
897
+ return;
898
+ }
899
+ const { square, type, color } = record;
900
+ const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
901
+ const location = posToLocation(square);
902
+ pieces[locationToString(location)] = {
903
+ id: `${square}-${pieceType}`,
904
+ type: pieceType,
905
+ side: color === "w" ? "white" : "black",
906
+ location
907
+ };
908
+ }));
909
+ return pieces;
910
+ };
911
+ var _diffPieces = (before, after) => {
807
912
  const difference = {
808
913
  removed: {},
809
914
  added: {}
@@ -826,28 +931,18 @@ var mapPieces = (before, after) => {
826
931
  piece.id = previous.id;
827
932
  }
828
933
  }
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
934
  return after;
841
935
  };
936
+ var createDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10).replace(/-/g, ".");
842
937
 
843
- // src/Chessboard/Chessboard.tsx
938
+ // src/components/Chessboard/Chessboard.tsx
844
939
  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";
940
+ import React16, { forwardRef as forwardRef2, memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
846
941
  import { useResizeDetector } from "react-resize-detector";
847
- import { useTrackProps as useTrackProps2 } from "@dxos/react-ui";
942
+ import { useForwardedRef, useTrackProps as useTrackProps2 } from "@dxos/react-ui";
848
943
  import { mx as mx4 } from "@dxos/react-ui-theme";
849
944
  import { isNotFalsy } from "@dxos/util";
850
- var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows = 8, cols = 8, classNames }) => {
945
+ var ChessboardComponent = /* @__PURE__ */ forwardRef2(({ classNames, orientation, showLabels, debug, rows = 8, cols = 8 }, forwardedRef) => {
851
946
  var _effect = _useSignals16();
852
947
  try {
853
948
  useTrackProps2({
@@ -855,11 +950,13 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
855
950
  showLabels,
856
951
  debug
857
952
  }, Chessboard.displayName, false);
858
- const { ref: containerRef, width, height } = useResizeDetector({
953
+ const targetRef = useForwardedRef(forwardedRef);
954
+ const { width, height } = useResizeDetector({
955
+ targetRef,
859
956
  refreshRate: 200
860
957
  });
861
- const { model, promoting, onPromotion } = useBoardContext();
862
- const locations = useMemo(() => {
958
+ const { model, promoting, onPromotion } = useGameboardContext(Chessboard.displayName);
959
+ const squares = useMemo(() => {
863
960
  return Array.from({
864
961
  length: rows
865
962
  }, (_, i) => orientation === "black" ? i : rows - 1 - i).flatMap((row) => Array.from({
@@ -874,19 +971,19 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
874
971
  cols
875
972
  ]);
876
973
  const layout = useMemo(() => {
877
- return locations.map((location) => {
974
+ return squares.map((location) => {
878
975
  return /* @__PURE__ */ React16.createElement("div", {
879
976
  key: locationToString(location),
880
977
  ["data-location"]: locationToString(location)
881
978
  });
882
979
  });
883
980
  }, [
884
- locations
981
+ squares
885
982
  ]);
886
983
  const [grid, setGrid] = useState4({});
887
984
  const gridRef = useRef3(null);
888
985
  useEffect4(() => {
889
- setGrid(locations.reduce((acc, location) => {
986
+ setGrid(squares.reduce((acc, location) => {
890
987
  const square = getSquareLocation(gridRef.current, location);
891
988
  const bounds = getRelativeBounds(gridRef.current, square);
892
989
  return {
@@ -895,7 +992,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
895
992
  };
896
993
  }, {}));
897
994
  }, [
898
- locations,
995
+ squares,
899
996
  width,
900
997
  height
901
998
  ]);
@@ -919,12 +1016,13 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
919
1016
  promoting
920
1017
  ]);
921
1018
  return /* @__PURE__ */ React16.createElement("div", {
922
- ref: containerRef,
923
- className: mx4("relative", classNames)
1019
+ ref: targetRef,
1020
+ tabIndex: 0,
1021
+ className: mx4("relative outline-none", classNames)
924
1022
  }, /* @__PURE__ */ React16.createElement("div", {
925
1023
  ref: gridRef,
926
1024
  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, {
1025
+ }, layout), /* @__PURE__ */ React16.createElement("div", null, squares.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
928
1026
  key: locationToString(location),
929
1027
  location,
930
1028
  label: showLabels ? locationToPos(location) : void 0,
@@ -932,14 +1030,14 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
932
1030
  classNames: getSquareColor(location)
933
1031
  }))), /* @__PURE__ */ React16.createElement("div", {
934
1032
  className: mx4(promoting && "opacity-50")
935
- }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Piece, {
1033
+ }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
936
1034
  key: piece.id,
937
1035
  piece,
938
1036
  bounds,
939
1037
  label: debug ? piece.id : void 0,
940
1038
  orientation,
941
1039
  Component: ChessPieces[piece.type]
942
- }))), /* @__PURE__ */ React16.createElement("div", null, promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
1040
+ }))), promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
943
1041
  grid,
944
1042
  piece: promoting,
945
1043
  onSelect: (piece) => {
@@ -950,15 +1048,13 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
950
1048
  promotion: piece.type
951
1049
  });
952
1050
  }
953
- })));
1051
+ }));
954
1052
  } finally {
955
1053
  _effect.f();
956
1054
  }
957
1055
  });
958
- Chessboard.displayName = "Chessboard";
959
- var getSquareLocation = (container, location) => {
960
- return container.querySelector(`[data-location="${locationToString(location)}"]`);
961
- };
1056
+ ChessboardComponent.displayName = "Chessboard";
1057
+ var Chessboard = /* @__PURE__ */ memo3(ChessboardComponent);
962
1058
  var PromotionSelector = ({ grid, piece, onSelect }) => {
963
1059
  var _effect = _useSignals16();
964
1060
  try {
@@ -988,29 +1084,30 @@ var PromotionSelector = ({ grid, piece, onSelect }) => {
988
1084
  type: selected.type
989
1085
  });
990
1086
  };
991
- return /* @__PURE__ */ React16.createElement("div", null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement("div", {
1087
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
992
1088
  key: piece2.id,
993
- style: bounds,
994
- onClick: () => handleSelect(piece2)
995
- }, /* @__PURE__ */ React16.createElement(Piece, {
1089
+ classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
996
1090
  piece: piece2,
997
1091
  bounds,
998
1092
  Component: ChessPieces[piece2.type],
999
- classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion)
1000
- }))));
1093
+ onClick: () => handleSelect(piece2)
1094
+ })));
1001
1095
  } finally {
1002
1096
  _effect.f();
1003
1097
  }
1004
1098
  };
1099
+ var getSquareLocation = (container, location) => {
1100
+ return container.querySelector(`[data-location="${locationToString(location)}"]`);
1101
+ };
1005
1102
  export {
1006
1103
  ChessModel,
1007
1104
  ChessPieces,
1008
1105
  Chessboard,
1009
1106
  Gameboard,
1010
- GameboardContext,
1011
- Piece,
1012
- Square,
1013
1107
  boardStyles,
1108
+ createChess,
1109
+ createPieceMap,
1110
+ getRawPgn,
1014
1111
  getRelativeBounds,
1015
1112
  getSquareColor,
1016
1113
  isEqualLocation,
@@ -1018,9 +1115,8 @@ export {
1018
1115
  isPiece,
1019
1116
  locationToPos,
1020
1117
  locationToString,
1021
- mapPieces,
1022
1118
  posToLocation,
1023
1119
  stringToLocation,
1024
- useBoardContext
1120
+ useGameboardContext
1025
1121
  };
1026
1122
  //# sourceMappingURL=index.mjs.map