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