@dxos/react-ui-gameboard 0.8.4-main.67995b8 → 0.8.4-main.a4bbb77

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