@dxos/react-ui-gameboard 0.8.4-main.5acf9ea → 0.8.4-main.5ea62a8

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