@dxos/react-ui-gameboard 0.8.4-main.3a94e84 → 0.8.4-main.3c1ae3b

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