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

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 +420 -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 +420 -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 +33 -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 +29 -27
  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,21 @@ 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
+ function _define_property(obj, key, value) {
662
+ if (key in obj) {
663
+ Object.defineProperty(obj, key, {
664
+ value,
665
+ enumerable: true,
666
+ configurable: true,
667
+ writable: true
668
+ });
669
+ } else {
670
+ obj[key] = value;
671
+ }
672
+ return obj;
673
+ }
674
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-gameboard/src/components/Chessboard/chess.ts";
671
675
  var ChessPieces = alpha_exports;
672
676
  var posToLocation = (pos) => {
673
677
  const col = pos.charCodeAt(0) - "a".charCodeAt(0);
@@ -680,6 +684,9 @@ var posToLocation = (pos) => {
680
684
  var locationToPos = ([row, col]) => {
681
685
  return String.fromCharCode(col + "a".charCodeAt(0)) + (row + 1);
682
686
  };
687
+ var getRawPgn = (pgn) => {
688
+ return pgn.replace(/\[.*?\]/g, "").trim();
689
+ };
683
690
  var styles = {
684
691
  neutral: {
685
692
  black: "bg-neutral-50",
@@ -706,55 +713,82 @@ var boardStyles = styles.original;
706
713
  var getSquareColor = ([row, col]) => {
707
714
  return (col + row) % 2 === 0 ? boardStyles.black : boardStyles.white;
708
715
  };
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;
716
+ var createChess = (pgn) => {
717
+ const chess = new ChessJS();
718
+ if (pgn) {
719
+ try {
720
+ chess.loadPgn(pgn);
721
+ } catch {
722
+ log4.warn(pgn, void 0, {
723
+ F: __dxlog_file4,
724
+ L: 76,
725
+ S: void 0,
726
+ C: (f, a) => f(...a)
727
+ });
728
+ }
732
729
  }
730
+ return chess;
733
731
  };
734
732
  var ChessModel = class {
735
- constructor(fen) {
736
- this._pieces = signal({});
737
- this.initialize(fen);
733
+ get readonly() {
734
+ return this._moveIndex.value !== this._chess.history().length;
738
735
  }
739
736
  get turn() {
740
- return this._game.turn() === "w" ? "white" : "black";
737
+ return this._chess.turn() === "w" ? "white" : "black";
738
+ }
739
+ get game() {
740
+ return this._chess;
741
741
  }
742
742
  get pieces() {
743
743
  return this._pieces;
744
744
  }
745
- get game() {
746
- return this._game;
745
+ get moveIndex() {
746
+ return this._moveIndex;
747
747
  }
748
748
  get fen() {
749
- return this._game.fen();
749
+ return this._chess.fen();
750
+ }
751
+ /**
752
+ * PGN with headers.
753
+ *
754
+ * [Event "?"]
755
+ * [Site "?"]
756
+ * [Date "2025.08.05"]
757
+ * [Round "?"]
758
+ * [White "?"]
759
+ * [Black "?"]
760
+ * [Result "*"]
761
+ */
762
+ // TODO(burdon): Update headers.
763
+ get pgn() {
764
+ return getRawPgn(this._chess.pgn());
750
765
  }
751
- initialize(fen) {
752
- this._pieces.value = {};
753
- this._game = new Chess(fen ? validateFen(fen).ok ? fen : void 0 : void 0);
754
- this._update();
766
+ setMoveIndex(index) {
767
+ const temp = new ChessJS();
768
+ const history = this._chess.history({
769
+ verbose: true
770
+ });
771
+ for (let i = 0; i < index && i < history.length; i++) {
772
+ temp.move(history[i]);
773
+ }
774
+ this._updateBoard(temp);
775
+ }
776
+ update(pgn = "") {
777
+ const previous = this._chess.history();
778
+ try {
779
+ this._chess.loadPgn(pgn);
780
+ this._chess.setHeader("Date", createDate());
781
+ this._chess.setHeader("Site", "dxos.org");
782
+ } catch {
783
+ }
784
+ const current = this._chess.history();
785
+ if (!isValidNextMove(previous, current)) {
786
+ this._pieces.value = {};
787
+ }
788
+ this._updateBoard(this._chess);
755
789
  }
756
790
  isValidMove(move) {
757
- return makeMove(new Chess(this._game.fen()), move) !== null;
791
+ return tryMove(new ChessJS(this._chess.fen()), move) !== null;
758
792
  }
759
793
  canPromote(move) {
760
794
  const isPawnMove = move.piece === "BP" || move.piece === "WP";
@@ -762,47 +796,110 @@ var ChessModel = class {
762
796
  return isPawnMove && isToLastRank;
763
797
  }
764
798
  makeMove(move) {
765
- const game = makeMove(this._game, move);
799
+ const game = tryMove(this._chess, move);
766
800
  if (!game) {
767
801
  return false;
768
802
  }
769
- this._game = game;
770
- this._update();
803
+ this._updateBoard(this._chess);
771
804
  return true;
772
805
  }
773
806
  makeRandomMove() {
774
- const moves = this._game.moves();
807
+ const moves = this._chess.moves();
775
808
  if (!moves.length) {
776
809
  return false;
777
810
  }
778
811
  const move = moves[Math.floor(Math.random() * moves.length)];
779
- this._game.move(move);
780
- this._update();
812
+ this._chess.move(move);
813
+ this._updateBoard(this._chess);
781
814
  return true;
782
815
  }
783
816
  /**
784
817
  * Update pieces preserving identity.
785
818
  */
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);
819
+ _updateBoard(chess) {
820
+ this._pieces.value = createPieceMap(chess);
821
+ this._moveIndex.value = chess.history().length;
822
+ }
823
+ constructor(pgn) {
824
+ _define_property(this, "_chess", new ChessJS());
825
+ _define_property(this, "_pieces", signal({}));
826
+ _define_property(this, "_moveIndex", signal(0));
827
+ this.update(pgn);
828
+ }
829
+ };
830
+ var tryMove = (chess, move) => {
831
+ const from = locationToPos(move.from);
832
+ const to = locationToPos(move.to);
833
+ try {
834
+ const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
835
+ chess.move({
836
+ from,
837
+ to,
838
+ promotion
839
+ }, {
840
+ strict: false
841
+ });
842
+ return chess;
843
+ } catch {
844
+ return null;
845
+ }
846
+ };
847
+ var isValidNextMove = (previous, current) => {
848
+ if (current.length > previous.length + 1) {
849
+ return false;
850
+ }
851
+ for (let i = 0; i < previous.length; i++) {
852
+ if (previous[i] !== current[i]) {
853
+ return false;
854
+ }
855
+ }
856
+ return true;
857
+ };
858
+ var createPieceMap = (chess) => {
859
+ const temp = new ChessJS();
860
+ let pieces = _createPieceMap(temp);
861
+ const history = chess.history({
862
+ verbose: true
863
+ });
864
+ for (let i = 0; i < history.length; i++) {
865
+ const move = history[i];
866
+ temp.move(move);
867
+ pieces = _diffPieces(pieces, _createPieceMap(temp));
868
+ const test = /* @__PURE__ */ new Set();
869
+ Object.values(pieces).forEach((piece) => {
870
+ invariant3(!test.has(piece.id), "Duplicate: " + piece.id, {
871
+ F: __dxlog_file4,
872
+ L: 252,
873
+ S: void 0,
874
+ A: [
875
+ "!test.has(piece.id)",
876
+ "'Duplicate: ' + piece.id"
877
+ ]
878
+ });
879
+ test.add(piece.id);
880
+ });
803
881
  }
882
+ return pieces;
804
883
  };
805
- var mapPieces = (before, after) => {
884
+ var _createPieceMap = (chess) => {
885
+ const pieces = {};
886
+ chess.board().flatMap((row) => row.forEach((record) => {
887
+ if (!record) {
888
+ return;
889
+ }
890
+ const { square, type, color } = record;
891
+ const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
892
+ const location = posToLocation(square);
893
+ pieces[locationToString(location)] = {
894
+ id: `${square}-${pieceType}`,
895
+ type: pieceType,
896
+ side: color === "w" ? "white" : "black",
897
+ location
898
+ };
899
+ }));
900
+ return pieces;
901
+ };
902
+ var _diffPieces = (before, after) => {
806
903
  const difference = {
807
904
  removed: {},
808
905
  added: {}
@@ -825,40 +922,27 @@ var mapPieces = (before, after) => {
825
922
  piece.id = previous.id;
826
923
  }
827
924
  }
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
925
  return after;
840
926
  };
927
+ var createDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10).replace(/-/g, ".");
841
928
 
842
- // src/Chessboard/Chessboard.tsx
929
+ // src/components/Chessboard/Chessboard.tsx
843
930
  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";
931
+ import React16, { forwardRef as forwardRef2, memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
845
932
  import { useResizeDetector } from "react-resize-detector";
846
- import { useTrackProps as useTrackProps2 } from "@dxos/react-ui";
933
+ import { useForwardedRef } from "@dxos/react-ui";
847
934
  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 }) => {
935
+ import { isTruthy } from "@dxos/util";
936
+ var ChessboardComponent = /* @__PURE__ */ forwardRef2(({ classNames, orientation, showLabels, debug, rows = 8, cols = 8 }, forwardedRef) => {
850
937
  var _effect = _useSignals16();
851
938
  try {
852
- useTrackProps2({
853
- orientation,
854
- showLabels,
855
- debug
856
- }, Chessboard.displayName, false);
857
- const { ref: containerRef, width, height } = useResizeDetector({
939
+ const targetRef = useForwardedRef(forwardedRef);
940
+ const { width, height } = useResizeDetector({
941
+ targetRef,
858
942
  refreshRate: 200
859
943
  });
860
- const { model, promoting, onPromotion } = useBoardContext();
861
- const locations = useMemo(() => {
944
+ const { model, promoting, onPromotion } = useGameboardContext(Chessboard.displayName);
945
+ const squares = useMemo(() => {
862
946
  return Array.from({
863
947
  length: rows
864
948
  }, (_, i) => orientation === "black" ? i : rows - 1 - i).flatMap((row) => Array.from({
@@ -873,19 +957,19 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
873
957
  cols
874
958
  ]);
875
959
  const layout = useMemo(() => {
876
- return locations.map((location) => {
960
+ return squares.map((location) => {
877
961
  return /* @__PURE__ */ React16.createElement("div", {
878
962
  key: locationToString(location),
879
963
  ["data-location"]: locationToString(location)
880
964
  });
881
965
  });
882
966
  }, [
883
- locations
967
+ squares
884
968
  ]);
885
969
  const [grid, setGrid] = useState4({});
886
970
  const gridRef = useRef3(null);
887
971
  useEffect4(() => {
888
- setGrid(locations.reduce((acc, location) => {
972
+ setGrid(squares.reduce((acc, location) => {
889
973
  const square = getSquareLocation(gridRef.current, location);
890
974
  const bounds = getRelativeBounds(gridRef.current, square);
891
975
  return {
@@ -894,7 +978,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
894
978
  };
895
979
  }, {}));
896
980
  }, [
897
- locations,
981
+ squares,
898
982
  width,
899
983
  height
900
984
  ]);
@@ -911,19 +995,20 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
911
995
  piece,
912
996
  bounds
913
997
  };
914
- }).filter(isNotFalsy);
998
+ }).filter(isTruthy);
915
999
  }, [
916
1000
  grid,
917
1001
  model?.pieces.value,
918
1002
  promoting
919
1003
  ]);
920
1004
  return /* @__PURE__ */ React16.createElement("div", {
921
- ref: containerRef,
922
- className: mx4("relative", classNames)
1005
+ ref: targetRef,
1006
+ tabIndex: 0,
1007
+ className: mx4("relative outline-none", classNames)
923
1008
  }, /* @__PURE__ */ React16.createElement("div", {
924
1009
  ref: gridRef,
925
1010
  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, {
1011
+ }, layout), /* @__PURE__ */ React16.createElement("div", null, squares.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
927
1012
  key: locationToString(location),
928
1013
  location,
929
1014
  label: showLabels ? locationToPos(location) : void 0,
@@ -931,14 +1016,14 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
931
1016
  classNames: getSquareColor(location)
932
1017
  }))), /* @__PURE__ */ React16.createElement("div", {
933
1018
  className: mx4(promoting && "opacity-50")
934
- }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Piece, {
1019
+ }, positions.map(({ bounds, piece }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
935
1020
  key: piece.id,
936
1021
  piece,
937
1022
  bounds,
938
1023
  label: debug ? piece.id : void 0,
939
1024
  orientation,
940
1025
  Component: ChessPieces[piece.type]
941
- }))), /* @__PURE__ */ React16.createElement("div", null, promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
1026
+ }))), promoting && /* @__PURE__ */ React16.createElement(PromotionSelector, {
942
1027
  grid,
943
1028
  piece: promoting,
944
1029
  onSelect: (piece) => {
@@ -949,15 +1034,13 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
949
1034
  promotion: piece.type
950
1035
  });
951
1036
  }
952
- })));
1037
+ }));
953
1038
  } finally {
954
1039
  _effect.f();
955
1040
  }
956
1041
  });
957
- Chessboard.displayName = "Chessboard";
958
- var getSquareLocation = (container, location) => {
959
- return container.querySelector(`[data-location="${locationToString(location)}"]`);
960
- };
1042
+ ChessboardComponent.displayName = "Chessboard";
1043
+ var Chessboard = /* @__PURE__ */ memo3(ChessboardComponent);
961
1044
  var PromotionSelector = ({ grid, piece, onSelect }) => {
962
1045
  var _effect = _useSignals16();
963
1046
  try {
@@ -987,29 +1070,30 @@ var PromotionSelector = ({ grid, piece, onSelect }) => {
987
1070
  type: selected.type
988
1071
  });
989
1072
  };
990
- return /* @__PURE__ */ React16.createElement("div", null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement("div", {
1073
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
991
1074
  key: piece2.id,
992
- style: bounds,
993
- onClick: () => handleSelect(piece2)
994
- }, /* @__PURE__ */ React16.createElement(Piece, {
1075
+ classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
995
1076
  piece: piece2,
996
1077
  bounds,
997
1078
  Component: ChessPieces[piece2.type],
998
- classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion)
999
- }))));
1079
+ onClick: () => handleSelect(piece2)
1080
+ })));
1000
1081
  } finally {
1001
1082
  _effect.f();
1002
1083
  }
1003
1084
  };
1085
+ var getSquareLocation = (container, location) => {
1086
+ return container.querySelector(`[data-location="${locationToString(location)}"]`);
1087
+ };
1004
1088
  export {
1005
1089
  ChessModel,
1006
1090
  ChessPieces,
1007
1091
  Chessboard,
1008
1092
  Gameboard,
1009
- GameboardContext,
1010
- Piece,
1011
- Square,
1012
1093
  boardStyles,
1094
+ createChess,
1095
+ createPieceMap,
1096
+ getRawPgn,
1013
1097
  getRelativeBounds,
1014
1098
  getSquareColor,
1015
1099
  isEqualLocation,
@@ -1017,9 +1101,8 @@ export {
1017
1101
  isPiece,
1018
1102
  locationToPos,
1019
1103
  locationToString,
1020
- mapPieces,
1021
1104
  posToLocation,
1022
1105
  stringToLocation,
1023
- useBoardContext
1106
+ useGameboardContext
1024
1107
  };
1025
1108
  //# sourceMappingURL=index.mjs.map