@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
@@ -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);
@@ -707,55 +719,79 @@ var boardStyles = styles.original;
707
719
  var getSquareColor = ([row, col]) => {
708
720
  return (col + row) % 2 === 0 ? boardStyles.black : boardStyles.white;
709
721
  };
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;
722
+ var createChess = (pgn) => {
723
+ const chess = new ChessJS();
724
+ if (pgn) {
725
+ try {
726
+ chess.loadPgn(pgn);
727
+ } catch {
728
+ log4.warn(pgn, void 0, {
729
+ F: __dxlog_file4,
730
+ L: 72,
731
+ S: void 0,
732
+ C: (f, a) => f(...a)
733
+ });
734
+ }
733
735
  }
736
+ return chess;
734
737
  };
735
738
  var ChessModel = class {
736
- constructor(fen) {
737
- this._pieces = signal({});
738
- this.initialize(fen);
739
+ get readonly() {
740
+ return this._moveIndex.value !== this._chess.history().length;
739
741
  }
740
742
  get turn() {
741
- return this._game.turn() === "w" ? "white" : "black";
743
+ return this._chess.turn() === "w" ? "white" : "black";
744
+ }
745
+ get game() {
746
+ return this._chess;
742
747
  }
743
748
  get pieces() {
744
749
  return this._pieces;
745
750
  }
746
- get game() {
747
- return this._game;
751
+ get moveIndex() {
752
+ return this._moveIndex;
748
753
  }
749
- get fen() {
750
- return this._game.fen();
754
+ /**
755
+ * PGN with headers.
756
+ *
757
+ * [Event "?"]
758
+ * [Site "?"]
759
+ * [Date "2025.08.05"]
760
+ * [Round "?"]
761
+ * [White "?"]
762
+ * [Black "?"]
763
+ * [Result "*"]
764
+ */
765
+ // TODO(burdon): Update headers.
766
+ get pgn() {
767
+ return this._chess.pgn();
751
768
  }
752
- initialize(fen) {
753
- this._pieces.value = {};
754
- this._game = new Chess(fen ? validateFen(fen).ok ? fen : void 0 : void 0);
755
- this._update();
769
+ setMoveIndex(index) {
770
+ const temp = new ChessJS();
771
+ const history = this._chess.history({
772
+ verbose: true
773
+ });
774
+ for (let i = 0; i < index && i < history.length; i++) {
775
+ temp.move(history[i]);
776
+ }
777
+ this._updateBoard(temp);
778
+ }
779
+ update(pgn = "") {
780
+ const previous = this._chess.history();
781
+ try {
782
+ this._chess.loadPgn(pgn);
783
+ this._chess.setHeader("Date", createDate());
784
+ this._chess.setHeader("Site", "dxos.org");
785
+ } catch {
786
+ }
787
+ const current = this._chess.history();
788
+ if (!isValidNextMove(previous, current)) {
789
+ this._pieces.value = {};
790
+ }
791
+ this._updateBoard(this._chess);
756
792
  }
757
793
  isValidMove(move) {
758
- return makeMove(new Chess(this._game.fen()), move) !== null;
794
+ return tryMove(new ChessJS(this._chess.fen()), move) !== null;
759
795
  }
760
796
  canPromote(move) {
761
797
  const isPawnMove = move.piece === "BP" || move.piece === "WP";
@@ -763,47 +799,110 @@ var ChessModel = class {
763
799
  return isPawnMove && isToLastRank;
764
800
  }
765
801
  makeMove(move) {
766
- const game = makeMove(this._game, move);
802
+ const game = tryMove(this._chess, move);
767
803
  if (!game) {
768
804
  return false;
769
805
  }
770
- this._game = game;
771
- this._update();
806
+ this._updateBoard(this._chess);
772
807
  return true;
773
808
  }
774
809
  makeRandomMove() {
775
- const moves = this._game.moves();
810
+ const moves = this._chess.moves();
776
811
  if (!moves.length) {
777
812
  return false;
778
813
  }
779
814
  const move = moves[Math.floor(Math.random() * moves.length)];
780
- this._game.move(move);
781
- this._update();
815
+ this._chess.move(move);
816
+ this._updateBoard(this._chess);
782
817
  return true;
783
818
  }
784
819
  /**
785
820
  * Update pieces preserving identity.
786
821
  */
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);
822
+ _updateBoard(chess) {
823
+ this._pieces.value = createPieceMap(chess);
824
+ this._moveIndex.value = chess.history().length;
825
+ }
826
+ constructor(pgn) {
827
+ _define_property(this, "_chess", new ChessJS());
828
+ _define_property(this, "_pieces", signal({}));
829
+ _define_property(this, "_moveIndex", signal(0));
830
+ this.update(pgn);
831
+ }
832
+ };
833
+ var tryMove = (chess, move) => {
834
+ const from = locationToPos(move.from);
835
+ const to = locationToPos(move.to);
836
+ try {
837
+ const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
838
+ chess.move({
839
+ from,
840
+ to,
841
+ promotion
842
+ }, {
843
+ strict: false
844
+ });
845
+ return chess;
846
+ } catch {
847
+ return null;
848
+ }
849
+ };
850
+ var isValidNextMove = (previous, current) => {
851
+ if (current.length > previous.length + 1) {
852
+ return false;
853
+ }
854
+ for (let i = 0; i < previous.length; i++) {
855
+ if (previous[i] !== current[i]) {
856
+ return false;
857
+ }
804
858
  }
859
+ return true;
860
+ };
861
+ var createPieceMap = (chess) => {
862
+ const temp = new ChessJS();
863
+ let pieces = _createPieceMap(temp);
864
+ const history = chess.history({
865
+ verbose: true
866
+ });
867
+ for (let i = 0; i < history.length; i++) {
868
+ const move = history[i];
869
+ temp.move(move);
870
+ pieces = _diffPieces(pieces, _createPieceMap(temp));
871
+ const test = /* @__PURE__ */ new Set();
872
+ Object.values(pieces).forEach((piece) => {
873
+ invariant3(!test.has(piece.id), "Duplicate: " + piece.id, {
874
+ F: __dxlog_file4,
875
+ L: 244,
876
+ S: void 0,
877
+ A: [
878
+ "!test.has(piece.id)",
879
+ "'Duplicate: ' + piece.id"
880
+ ]
881
+ });
882
+ test.add(piece.id);
883
+ });
884
+ }
885
+ return pieces;
886
+ };
887
+ var _createPieceMap = (chess) => {
888
+ const pieces = {};
889
+ chess.board().flatMap((row) => row.forEach((record) => {
890
+ if (!record) {
891
+ return;
892
+ }
893
+ const { square, type, color } = record;
894
+ const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
895
+ const location = posToLocation(square);
896
+ pieces[locationToString(location)] = {
897
+ id: `${square}-${pieceType}`,
898
+ type: pieceType,
899
+ side: color === "w" ? "white" : "black",
900
+ location
901
+ };
902
+ }));
903
+ return pieces;
805
904
  };
806
- var mapPieces = (before, after) => {
905
+ var _diffPieces = (before, after) => {
807
906
  const difference = {
808
907
  removed: {},
809
908
  added: {}
@@ -826,23 +925,13 @@ var mapPieces = (before, after) => {
826
925
  piece.id = previous.id;
827
926
  }
828
927
  }
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
928
  return after;
841
929
  };
930
+ var createDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10).replace(/-/g, ".");
842
931
 
843
- // src/Chessboard/Chessboard.tsx
932
+ // src/components/Chessboard/Chessboard.tsx
844
933
  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";
934
+ import React16, { memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
846
935
  import { useResizeDetector } from "react-resize-detector";
847
936
  import { useTrackProps as useTrackProps2 } from "@dxos/react-ui";
848
937
  import { mx as mx4 } from "@dxos/react-ui-theme";
@@ -858,8 +947,8 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
858
947
  const { ref: containerRef, width, height } = useResizeDetector({
859
948
  refreshRate: 200
860
949
  });
861
- const { model, promoting, onPromotion } = useBoardContext();
862
- const locations = useMemo(() => {
950
+ const { model, promoting, onPromotion } = useGameboardContext(Chessboard.displayName);
951
+ const squares = useMemo(() => {
863
952
  return Array.from({
864
953
  length: rows
865
954
  }, (_, i) => orientation === "black" ? i : rows - 1 - i).flatMap((row) => Array.from({
@@ -874,19 +963,19 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
874
963
  cols
875
964
  ]);
876
965
  const layout = useMemo(() => {
877
- return locations.map((location) => {
966
+ return squares.map((location) => {
878
967
  return /* @__PURE__ */ React16.createElement("div", {
879
968
  key: locationToString(location),
880
969
  ["data-location"]: locationToString(location)
881
970
  });
882
971
  });
883
972
  }, [
884
- locations
973
+ squares
885
974
  ]);
886
975
  const [grid, setGrid] = useState4({});
887
976
  const gridRef = useRef3(null);
888
977
  useEffect4(() => {
889
- setGrid(locations.reduce((acc, location) => {
978
+ setGrid(squares.reduce((acc, location) => {
890
979
  const square = getSquareLocation(gridRef.current, location);
891
980
  const bounds = getRelativeBounds(gridRef.current, square);
892
981
  return {
@@ -895,7 +984,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
895
984
  };
896
985
  }, {}));
897
986
  }, [
898
- locations,
987
+ squares,
899
988
  width,
900
989
  height
901
990
  ]);
@@ -924,7 +1013,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
924
1013
  }, /* @__PURE__ */ React16.createElement("div", {
925
1014
  ref: gridRef,
926
1015
  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, {
1016
+ }, layout), /* @__PURE__ */ React16.createElement("div", null, squares.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
928
1017
  key: locationToString(location),
929
1018
  location,
930
1019
  label: showLabels ? locationToPos(location) : void 0,
@@ -932,14 +1021,14 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
932
1021
  classNames: getSquareColor(location)
933
1022
  }))), /* @__PURE__ */ React16.createElement("div", {
934
1023
  className: mx4(promoting && "opacity-50")
935
- }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Piece, {
1024
+ }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
936
1025
  key: piece.id,
937
1026
  piece,
938
1027
  bounds,
939
1028
  label: debug ? piece.id : void 0,
940
1029
  orientation,
941
1030
  Component: ChessPieces[piece.type]
942
- }))), /* @__PURE__ */ React16.createElement("div", null, promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
1031
+ }))), promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
943
1032
  grid,
944
1033
  piece: promoting,
945
1034
  onSelect: (piece) => {
@@ -950,7 +1039,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
950
1039
  promotion: piece.type
951
1040
  });
952
1041
  }
953
- })));
1042
+ }));
954
1043
  } finally {
955
1044
  _effect.f();
956
1045
  }
@@ -988,16 +1077,14 @@ var PromotionSelector = ({ grid, piece, onSelect }) => {
988
1077
  type: selected.type
989
1078
  });
990
1079
  };
991
- return /* @__PURE__ */ React16.createElement("div", null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement("div", {
1080
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
992
1081
  key: piece2.id,
993
- style: bounds,
994
- onClick: () => handleSelect(piece2)
995
- }, /* @__PURE__ */ React16.createElement(Piece, {
996
1082
  piece: piece2,
997
1083
  bounds,
998
1084
  Component: ChessPieces[piece2.type],
999
- classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion)
1000
- }))));
1085
+ classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
1086
+ onClick: () => handleSelect(piece2)
1087
+ })));
1001
1088
  } finally {
1002
1089
  _effect.f();
1003
1090
  }
@@ -1007,10 +1094,9 @@ export {
1007
1094
  ChessPieces,
1008
1095
  Chessboard,
1009
1096
  Gameboard,
1010
- GameboardContext,
1011
- Piece,
1012
- Square,
1013
1097
  boardStyles,
1098
+ createChess,
1099
+ createPieceMap,
1014
1100
  getRelativeBounds,
1015
1101
  getSquareColor,
1016
1102
  isEqualLocation,
@@ -1018,9 +1104,8 @@ export {
1018
1104
  isPiece,
1019
1105
  locationToPos,
1020
1106
  locationToString,
1021
- mapPieces,
1022
1107
  posToLocation,
1023
1108
  stringToLocation,
1024
- useBoardContext
1109
+ useGameboardContext
1025
1110
  };
1026
1111
  //# sourceMappingURL=index.mjs.map