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

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