@dxos/react-ui-gameboard 0.8.4-main.fd6878d → 0.8.4-main.fffef41

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 (29) hide show
  1. package/dist/lib/browser/index.mjs +133 -98
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +133 -98
  5. package/dist/lib/node-esm/index.mjs.map +3 -3
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Chessboard/Chessboard.d.ts +9 -4
  8. package/dist/types/src/components/Chessboard/Chessboard.d.ts.map +1 -1
  9. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts +20 -18
  10. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Chessboard/chess.d.ts +12 -6
  12. package/dist/types/src/components/Chessboard/chess.d.ts.map +1 -1
  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/Gameboard/Gameboard.d.ts +3 -2
  16. package/dist/types/src/components/Gameboard/Gameboard.d.ts.map +1 -1
  17. package/dist/types/src/components/Gameboard/Piece.d.ts +1 -1
  18. package/dist/types/src/components/Gameboard/Piece.d.ts.map +1 -1
  19. package/dist/types/src/components/Gameboard/types.d.ts +1 -0
  20. package/dist/types/src/components/Gameboard/types.d.ts.map +1 -1
  21. package/dist/types/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +19 -19
  23. package/src/components/Chessboard/Chessboard.stories.tsx +13 -15
  24. package/src/components/Chessboard/Chessboard.tsx +28 -21
  25. package/src/components/Chessboard/chess.test.ts +19 -0
  26. package/src/components/Chessboard/chess.ts +103 -54
  27. package/src/components/Gameboard/Gameboard.tsx +2 -1
  28. package/src/components/Gameboard/Piece.tsx +8 -5
  29. package/src/components/Gameboard/types.ts +1 -0
@@ -40,20 +40,12 @@ import React, { memo, useEffect, useRef, useState } from "react";
40
40
  import { createPortal } from "react-dom";
41
41
  import { invariant } from "@dxos/invariant";
42
42
  import { log } from "@dxos/log";
43
- import { useDynamicRef, useTrackProps } from "@dxos/react-ui";
43
+ import { useDynamicRef } from "@dxos/react-ui";
44
44
  import { mx } from "@dxos/react-ui-theme";
45
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 }) => {
46
+ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, bounds, label, onClick }) => {
47
47
  var _effect = _useSignals();
48
48
  try {
49
- useTrackProps({
50
- classNames,
51
- Component,
52
- piece,
53
- orientation,
54
- bounds,
55
- label
56
- }, Piece.displayName, false);
57
49
  const { model, dragging: isDragging, promoting } = useGameboardContext(Piece.displayName);
58
50
  const promotingRef = useDynamicRef(promoting);
59
51
  const [dragging, setDragging] = useState(false);
@@ -61,10 +53,13 @@ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, orientation, b
61
53
  const [current, setCurrent] = useState({});
62
54
  const ref = useRef(null);
63
55
  useEffect(() => {
56
+ if (!model) {
57
+ return;
58
+ }
64
59
  const el = ref.current;
65
60
  invariant(el, void 0, {
66
61
  F: __dxlog_file,
67
- L: 42,
62
+ L: 45,
68
63
  S: void 0,
69
64
  A: [
70
65
  "el",
@@ -81,7 +76,7 @@ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, orientation, b
81
76
  source: source.data
82
77
  }, {
83
78
  F: __dxlog_file,
84
- L: 48,
79
+ L: 51,
85
80
  S: void 0,
86
81
  C: (f, a) => f(...a)
87
82
  });
@@ -99,7 +94,7 @@ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, orientation, b
99
94
  nativeSetDragImage
100
95
  });
101
96
  },
102
- canDrag: () => !promotingRef.current && model?.turn === piece.side,
97
+ canDrag: () => !promotingRef.current && !model.readonly && model.turn === piece.side,
103
98
  onDragStart: () => setDragging(true),
104
99
  onDrop: ({ location: { current: current2 } }) => {
105
100
  try {
@@ -125,7 +120,7 @@ var Piece = /* @__PURE__ */ memo(({ classNames, Component, piece, orientation, b
125
120
  return;
126
121
  }
127
122
  if (!current.location || !isEqualLocation(current.location, piece.location)) {
128
- ref.current.style.transition = "top 400ms ease-out, left 400ms ease-out";
123
+ ref.current.style.transition = "top 250ms ease-out, left 250ms ease-out";
129
124
  ref.current.style.top = bounds.top + "px";
130
125
  ref.current.style.left = bounds.left + "px";
131
126
  setCurrent({
@@ -260,7 +255,7 @@ var [GameboardContextProvider, useRadixGameboardContext] = createContext("Gamebo
260
255
  var useGameboardContext = (consumerName) => {
261
256
  return useRadixGameboardContext(consumerName);
262
257
  };
263
- var GameboardRoot = ({ children, model, onDrop }) => {
258
+ var GameboardRoot = ({ children, model, moveNumber, onDrop }) => {
264
259
  var _effect = _useSignals3();
265
260
  try {
266
261
  const [dragging, setDragging] = useState3(false);
@@ -270,7 +265,7 @@ var GameboardRoot = ({ children, model, onDrop }) => {
270
265
  move
271
266
  }, {
272
267
  F: __dxlog_file3,
273
- L: 47,
268
+ L: 48,
274
269
  S: void 0,
275
270
  C: (f, a) => f(...a)
276
271
  });
@@ -287,7 +282,7 @@ var GameboardRoot = ({ children, model, onDrop }) => {
287
282
  source
288
283
  }, {
289
284
  F: __dxlog_file3,
290
- L: 60,
285
+ L: 61,
291
286
  S: void 0,
292
287
  C: (f, a) => f(...a)
293
288
  });
@@ -299,7 +294,7 @@ var GameboardRoot = ({ children, model, onDrop }) => {
299
294
  location
300
295
  }, {
301
296
  F: __dxlog_file3,
302
- L: 64,
297
+ L: 65,
303
298
  S: void 0,
304
299
  C: (f, a) => f(...a)
305
300
  });
@@ -369,6 +364,7 @@ var Gameboard = {
369
364
  // src/components/Chessboard/chess.ts
370
365
  import { signal } from "@preact/signals-core";
371
366
  import { Chess as ChessJS } from "chess.js";
367
+ import { invariant as invariant3 } from "@dxos/invariant";
372
368
  import { log as log4 } from "@dxos/log";
373
369
 
374
370
  // src/gen/pieces/chess/alpha/index.ts
@@ -675,6 +671,9 @@ var posToLocation = (pos) => {
675
671
  var locationToPos = ([row, col]) => {
676
672
  return String.fromCharCode(col + "a".charCodeAt(0)) + (row + 1);
677
673
  };
674
+ var getRawPgn = (pgn) => {
675
+ return pgn.replace(/\[.*?\]/g, "").trim();
676
+ };
678
677
  var styles = {
679
678
  neutral: {
680
679
  black: "bg-neutral-50",
@@ -709,7 +708,7 @@ var createChess = (pgn) => {
709
708
  } catch {
710
709
  log4.warn(pgn, void 0, {
711
710
  F: __dxlog_file4,
712
- L: 71,
711
+ L: 76,
713
712
  S: void 0,
714
713
  C: (f, a) => f(...a)
715
714
  });
@@ -717,37 +716,30 @@ var createChess = (pgn) => {
717
716
  }
718
717
  return chess;
719
718
  };
720
- var tryMove = (chess, move) => {
721
- const from = locationToPos(move.from);
722
- const to = locationToPos(move.to);
723
- try {
724
- const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
725
- chess.move({
726
- from,
727
- to,
728
- promotion
729
- }, {
730
- strict: false
731
- });
732
- return chess;
733
- } catch {
734
- return null;
735
- }
736
- };
737
719
  var ChessModel = class {
738
720
  _chess = new ChessJS();
739
721
  _pieces = signal({});
722
+ _moveIndex = signal(0);
740
723
  constructor(pgn) {
741
724
  this.update(pgn);
742
725
  }
726
+ get readonly() {
727
+ return this._moveIndex.value !== this._chess.history().length;
728
+ }
743
729
  get turn() {
744
730
  return this._chess.turn() === "w" ? "white" : "black";
745
731
  }
732
+ get game() {
733
+ return this._chess;
734
+ }
746
735
  get pieces() {
747
736
  return this._pieces;
748
737
  }
749
- get game() {
750
- return this._chess;
738
+ get moveIndex() {
739
+ return this._moveIndex;
740
+ }
741
+ get fen() {
742
+ return this._chess.fen();
751
743
  }
752
744
  /**
753
745
  * PGN with headers.
@@ -762,10 +754,17 @@ var ChessModel = class {
762
754
  */
763
755
  // TODO(burdon): Update headers.
764
756
  get pgn() {
765
- return this._chess.pgn();
757
+ return getRawPgn(this._chess.pgn());
766
758
  }
767
- get fen() {
768
- return this._chess.fen();
759
+ setMoveIndex(index) {
760
+ const temp = new ChessJS();
761
+ const history = this._chess.history({
762
+ verbose: true
763
+ });
764
+ for (let i = 0; i < index && i < history.length; i++) {
765
+ temp.move(history[i]);
766
+ }
767
+ this._updateBoard(temp);
769
768
  }
770
769
  update(pgn = "") {
771
770
  const previous = this._chess.history();
@@ -779,7 +778,7 @@ var ChessModel = class {
779
778
  if (!isValidNextMove(previous, current)) {
780
779
  this._pieces.value = {};
781
780
  }
782
- this._update();
781
+ this._updateBoard(this._chess);
783
782
  }
784
783
  isValidMove(move) {
785
784
  return tryMove(new ChessJS(this._chess.fen()), move) !== null;
@@ -794,7 +793,7 @@ var ChessModel = class {
794
793
  if (!game) {
795
794
  return false;
796
795
  }
797
- this._update();
796
+ this._updateBoard(this._chess);
798
797
  return true;
799
798
  }
800
799
  makeRandomMove() {
@@ -804,29 +803,32 @@ var ChessModel = class {
804
803
  }
805
804
  const move = moves[Math.floor(Math.random() * moves.length)];
806
805
  this._chess.move(move);
807
- this._update();
806
+ this._updateBoard(this._chess);
808
807
  return true;
809
808
  }
810
809
  /**
811
810
  * Update pieces preserving identity.
812
811
  */
813
- _update() {
814
- const pieces = {};
815
- this._chess.board().flatMap((row) => row.forEach((record) => {
816
- if (!record) {
817
- return;
818
- }
819
- const { square, type, color } = record;
820
- const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
821
- const location = posToLocation(square);
822
- pieces[locationToString(location)] = {
823
- id: `${square}-${pieceType}`,
824
- type: pieceType,
825
- side: color === "w" ? "white" : "black",
826
- location
827
- };
828
- }));
829
- this._pieces.value = mapPieces(this._pieces.value, pieces);
812
+ _updateBoard(chess) {
813
+ this._pieces.value = createPieceMap(chess);
814
+ this._moveIndex.value = chess.history().length;
815
+ }
816
+ };
817
+ var tryMove = (chess, move) => {
818
+ const from = locationToPos(move.from);
819
+ const to = locationToPos(move.to);
820
+ try {
821
+ const promotion = move.promotion ? move.promotion[1].toLowerCase() : "q";
822
+ chess.move({
823
+ from,
824
+ to,
825
+ promotion
826
+ }, {
827
+ strict: false
828
+ });
829
+ return chess;
830
+ } catch {
831
+ return null;
830
832
  }
831
833
  };
832
834
  var isValidNextMove = (previous, current) => {
@@ -840,7 +842,51 @@ var isValidNextMove = (previous, current) => {
840
842
  }
841
843
  return true;
842
844
  };
843
- var mapPieces = (before, after) => {
845
+ var createPieceMap = (chess) => {
846
+ const temp = new ChessJS();
847
+ let pieces = _createPieceMap(temp);
848
+ const history = chess.history({
849
+ verbose: true
850
+ });
851
+ for (let i = 0; i < history.length; i++) {
852
+ const move = history[i];
853
+ temp.move(move);
854
+ pieces = _diffPieces(pieces, _createPieceMap(temp));
855
+ const test = /* @__PURE__ */ new Set();
856
+ Object.values(pieces).forEach((piece) => {
857
+ invariant3(!test.has(piece.id), "Duplicate: " + piece.id, {
858
+ F: __dxlog_file4,
859
+ L: 252,
860
+ S: void 0,
861
+ A: [
862
+ "!test.has(piece.id)",
863
+ "'Duplicate: ' + piece.id"
864
+ ]
865
+ });
866
+ test.add(piece.id);
867
+ });
868
+ }
869
+ return pieces;
870
+ };
871
+ var _createPieceMap = (chess) => {
872
+ const pieces = {};
873
+ chess.board().flatMap((row) => row.forEach((record) => {
874
+ if (!record) {
875
+ return;
876
+ }
877
+ const { square, type, color } = record;
878
+ const pieceType = `${color.toUpperCase()}${type.toUpperCase()}`;
879
+ const location = posToLocation(square);
880
+ pieces[locationToString(location)] = {
881
+ id: `${square}-${pieceType}`,
882
+ type: pieceType,
883
+ side: color === "w" ? "white" : "black",
884
+ location
885
+ };
886
+ }));
887
+ return pieces;
888
+ };
889
+ var _diffPieces = (before, after) => {
844
890
  const difference = {
845
891
  removed: {},
846
892
  added: {}
@@ -863,41 +909,27 @@ var mapPieces = (before, after) => {
863
909
  piece.id = previous.id;
864
910
  }
865
911
  }
866
- log4("delta", {
867
- before: Object.keys(before).length,
868
- after: Object.keys(after).length,
869
- removed: Object.keys(difference.removed).length,
870
- added: Object.keys(difference.added).length
871
- }, {
872
- F: __dxlog_file4,
873
- L: 263,
874
- S: void 0,
875
- C: (f, a) => f(...a)
876
- });
877
912
  return after;
878
913
  };
879
914
  var createDate = (date = /* @__PURE__ */ new Date()) => date.toISOString().slice(0, 10).replace(/-/g, ".");
880
915
 
881
916
  // src/components/Chessboard/Chessboard.tsx
882
917
  import { useSignals as _useSignals16 } from "@preact-signals/safe-react/tracking";
883
- import React16, { memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
918
+ import React16, { forwardRef as forwardRef2, memo as memo3, useEffect as useEffect4, useMemo, useRef as useRef3, useState as useState4 } from "react";
884
919
  import { useResizeDetector } from "react-resize-detector";
885
- import { useTrackProps as useTrackProps2 } from "@dxos/react-ui";
920
+ import { useForwardedRef } from "@dxos/react-ui";
886
921
  import { mx as mx4 } from "@dxos/react-ui-theme";
887
- import { isNotFalsy } from "@dxos/util";
888
- var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows = 8, cols = 8, classNames }) => {
922
+ import { isNonNullable } from "@dxos/util";
923
+ var ChessboardComponent = /* @__PURE__ */ forwardRef2(({ classNames, orientation, showLabels, debug, rows = 8, cols = 8 }, forwardedRef) => {
889
924
  var _effect = _useSignals16();
890
925
  try {
891
- useTrackProps2({
892
- orientation,
893
- showLabels,
894
- debug
895
- }, Chessboard.displayName, false);
896
- const { ref: containerRef, width, height } = useResizeDetector({
926
+ const targetRef = useForwardedRef(forwardedRef);
927
+ const { width, height } = useResizeDetector({
928
+ targetRef,
897
929
  refreshRate: 200
898
930
  });
899
931
  const { model, promoting, onPromotion } = useGameboardContext(Chessboard.displayName);
900
- const locations = useMemo(() => {
932
+ const squares = useMemo(() => {
901
933
  return Array.from({
902
934
  length: rows
903
935
  }, (_, i) => orientation === "black" ? i : rows - 1 - i).flatMap((row) => Array.from({
@@ -912,19 +944,19 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
912
944
  cols
913
945
  ]);
914
946
  const layout = useMemo(() => {
915
- return locations.map((location) => {
947
+ return squares.map((location) => {
916
948
  return /* @__PURE__ */ React16.createElement("div", {
917
949
  key: locationToString(location),
918
950
  ["data-location"]: locationToString(location)
919
951
  });
920
952
  });
921
953
  }, [
922
- locations
954
+ squares
923
955
  ]);
924
956
  const [grid, setGrid] = useState4({});
925
957
  const gridRef = useRef3(null);
926
958
  useEffect4(() => {
927
- setGrid(locations.reduce((acc, location) => {
959
+ setGrid(squares.reduce((acc, location) => {
928
960
  const square = getSquareLocation(gridRef.current, location);
929
961
  const bounds = getRelativeBounds(gridRef.current, square);
930
962
  return {
@@ -933,7 +965,7 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
933
965
  };
934
966
  }, {}));
935
967
  }, [
936
- locations,
968
+ squares,
937
969
  width,
938
970
  height
939
971
  ]);
@@ -950,19 +982,20 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
950
982
  piece,
951
983
  bounds
952
984
  };
953
- }).filter(isNotFalsy);
985
+ }).filter(isNonNullable);
954
986
  }, [
955
987
  grid,
956
988
  model?.pieces.value,
957
989
  promoting
958
990
  ]);
959
991
  return /* @__PURE__ */ React16.createElement("div", {
960
- ref: containerRef,
961
- className: mx4("relative", classNames)
992
+ ref: targetRef,
993
+ tabIndex: 0,
994
+ className: mx4("relative outline-none", classNames)
962
995
  }, /* @__PURE__ */ React16.createElement("div", {
963
996
  ref: gridRef,
964
997
  className: "grid grid-rows-8 grid-cols-8 aspect-square select-none"
965
- }, layout), /* @__PURE__ */ React16.createElement("div", null, locations.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
998
+ }, layout), /* @__PURE__ */ React16.createElement("div", null, squares.map((location) => /* @__PURE__ */ React16.createElement(Gameboard.Square, {
966
999
  key: locationToString(location),
967
1000
  location,
968
1001
  label: showLabels ? locationToPos(location) : void 0,
@@ -993,10 +1026,8 @@ var Chessboard = /* @__PURE__ */ memo3(({ orientation, showLabels, debug, rows =
993
1026
  _effect.f();
994
1027
  }
995
1028
  });
996
- Chessboard.displayName = "Chessboard";
997
- var getSquareLocation = (container, location) => {
998
- return container.querySelector(`[data-location="${locationToString(location)}"]`);
999
- };
1029
+ ChessboardComponent.displayName = "Chessboard";
1030
+ var Chessboard = /* @__PURE__ */ memo3(ChessboardComponent);
1000
1031
  var PromotionSelector = ({ grid, piece, onSelect }) => {
1001
1032
  var _effect = _useSignals16();
1002
1033
  try {
@@ -1028,16 +1059,19 @@ var PromotionSelector = ({ grid, piece, onSelect }) => {
1028
1059
  };
1029
1060
  return /* @__PURE__ */ React16.createElement(React16.Fragment, null, positions.map(({ piece: piece2, bounds }) => /* @__PURE__ */ React16.createElement(Gameboard.Piece, {
1030
1061
  key: piece2.id,
1062
+ classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
1031
1063
  piece: piece2,
1032
1064
  bounds,
1033
1065
  Component: ChessPieces[piece2.type],
1034
- classNames: mx4("border-2 border-neutral-700 rounded-full", boardStyles.promotion),
1035
1066
  onClick: () => handleSelect(piece2)
1036
1067
  })));
1037
1068
  } finally {
1038
1069
  _effect.f();
1039
1070
  }
1040
1071
  };
1072
+ var getSquareLocation = (container, location) => {
1073
+ return container.querySelector(`[data-location="${locationToString(location)}"]`);
1074
+ };
1041
1075
  export {
1042
1076
  ChessModel,
1043
1077
  ChessPieces,
@@ -1045,6 +1079,8 @@ export {
1045
1079
  Gameboard,
1046
1080
  boardStyles,
1047
1081
  createChess,
1082
+ createPieceMap,
1083
+ getRawPgn,
1048
1084
  getRelativeBounds,
1049
1085
  getSquareColor,
1050
1086
  isEqualLocation,
@@ -1052,7 +1088,6 @@ export {
1052
1088
  isPiece,
1053
1089
  locationToPos,
1054
1090
  locationToString,
1055
- mapPieces,
1056
1091
  posToLocation,
1057
1092
  stringToLocation,
1058
1093
  useGameboardContext