@dxos/plugin-chess 0.8.4-main.ead640a → 0.8.4-main.effb148878

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 (246) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +526 -0
  3. package/README.md +1 -1
  4. package/dist/lib/neutral/ChessArticle-QLLKZGPH.mjs +88 -0
  5. package/dist/lib/neutral/ChessArticle-QLLKZGPH.mjs.map +7 -0
  6. package/dist/lib/neutral/ChessCard-I5RSZZIP.mjs +19 -0
  7. package/dist/lib/neutral/ChessCard-I5RSZZIP.mjs.map +7 -0
  8. package/dist/lib/neutral/ChessPlugin.mjs +10 -0
  9. package/dist/lib/neutral/ChessPlugin.node.mjs +23 -0
  10. package/dist/lib/neutral/ChessPlugin.node.mjs.map +7 -0
  11. package/dist/lib/neutral/ChessPlugin.workerd.mjs +21 -0
  12. package/dist/lib/neutral/ChessPlugin.workerd.mjs.map +7 -0
  13. package/dist/lib/neutral/blueprint-definition-PZXHAYLD.mjs +15 -0
  14. package/dist/lib/neutral/blueprint-definition-PZXHAYLD.mjs.map +7 -0
  15. package/dist/lib/neutral/blueprints/index.mjs +8 -0
  16. package/dist/lib/neutral/capabilities/index.mjs +13 -0
  17. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  18. package/dist/lib/neutral/chunk-2MAUPQEP.mjs +148 -0
  19. package/dist/lib/neutral/chunk-2MAUPQEP.mjs.map +7 -0
  20. package/dist/lib/neutral/chunk-47NTXLZ4.mjs +8 -0
  21. package/dist/lib/neutral/chunk-47NTXLZ4.mjs.map +7 -0
  22. package/dist/lib/neutral/chunk-HVIKFYAW.mjs +29 -0
  23. package/dist/lib/neutral/chunk-HVIKFYAW.mjs.map +7 -0
  24. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  25. package/dist/lib/neutral/chunk-V4UMIQ6K.mjs +31 -0
  26. package/dist/lib/neutral/chunk-V4UMIQ6K.mjs.map +7 -0
  27. package/dist/lib/neutral/chunk-XWKNXG62.mjs +34 -0
  28. package/dist/lib/neutral/chunk-XWKNXG62.mjs.map +7 -0
  29. package/dist/lib/neutral/components/index.mjs +239 -0
  30. package/dist/lib/neutral/components/index.mjs.map +7 -0
  31. package/dist/lib/neutral/containers/index.mjs +11 -0
  32. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  33. package/dist/lib/neutral/game-variant-UMYAK7LT.mjs +41 -0
  34. package/dist/lib/neutral/game-variant-UMYAK7LT.mjs.map +7 -0
  35. package/dist/lib/neutral/index.mjs +18 -0
  36. package/dist/lib/neutral/meta.json +1 -0
  37. package/dist/lib/neutral/meta.mjs +8 -0
  38. package/dist/lib/neutral/meta.mjs.map +7 -0
  39. package/dist/lib/neutral/move-CSQAYS6C.mjs +37 -0
  40. package/dist/lib/neutral/move-CSQAYS6C.mjs.map +7 -0
  41. package/dist/lib/neutral/operation-handler-EKZKFQUG.mjs +13 -0
  42. package/dist/lib/neutral/operation-handler-EKZKFQUG.mjs.map +7 -0
  43. package/dist/lib/neutral/operations/index.mjs +8 -0
  44. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  45. package/dist/lib/neutral/play-MAQS65O7.mjs +46 -0
  46. package/dist/lib/neutral/play-MAQS65O7.mjs.map +7 -0
  47. package/dist/lib/neutral/plugin.mjs +16 -0
  48. package/dist/lib/neutral/plugin.mjs.map +7 -0
  49. package/dist/lib/neutral/print-EAPXOLGE.mjs +29 -0
  50. package/dist/lib/neutral/print-EAPXOLGE.mjs.map +7 -0
  51. package/dist/lib/neutral/testing.mjs +8 -0
  52. package/dist/lib/neutral/testing.mjs.map +7 -0
  53. package/dist/lib/neutral/translations.mjs +35 -0
  54. package/dist/lib/neutral/translations.mjs.map +7 -0
  55. package/dist/lib/neutral/types/index.mjs +10 -0
  56. package/dist/lib/neutral/types/index.mjs.map +7 -0
  57. package/dist/types/src/ChessPlugin.d.ts +3 -1
  58. package/dist/types/src/ChessPlugin.d.ts.map +1 -1
  59. package/dist/types/src/ChessPlugin.node.d.ts +8 -0
  60. package/dist/types/src/ChessPlugin.node.d.ts.map +1 -0
  61. package/dist/types/src/ChessPlugin.test.d.ts +2 -0
  62. package/dist/types/src/ChessPlugin.test.d.ts.map +1 -0
  63. package/dist/types/src/ChessPlugin.workerd.d.ts +4 -0
  64. package/dist/types/src/ChessPlugin.workerd.d.ts.map +1 -0
  65. package/dist/types/src/blueprints/chess-blueprint.d.ts +4 -0
  66. package/dist/types/src/blueprints/chess-blueprint.d.ts.map +1 -0
  67. package/dist/types/src/blueprints/index.d.ts +2 -0
  68. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  69. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -5
  70. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/game-variant.d.ts +6 -0
  72. package/dist/types/src/capabilities/game-variant.d.ts.map +1 -0
  73. package/dist/types/src/capabilities/index.d.ts +5 -4
  74. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  75. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  76. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  77. package/dist/types/src/components/{Chessboard.d.ts → Chessboard/Chessboard.d.ts} +10 -10
  78. package/dist/types/src/components/Chessboard/Chessboard.d.ts.map +1 -0
  79. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts +57 -0
  80. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts.map +1 -0
  81. package/dist/types/src/components/{Info.d.ts → Chessboard/Info.d.ts} +5 -6
  82. package/dist/types/src/components/Chessboard/Info.d.ts.map +1 -0
  83. package/dist/types/src/components/Chessboard/index.d.ts +2 -0
  84. package/dist/types/src/components/Chessboard/index.d.ts.map +1 -0
  85. package/dist/types/src/components/Chessboard/types.d.ts +8 -0
  86. package/dist/types/src/components/Chessboard/types.d.ts.map +1 -0
  87. package/dist/types/src/components/index.d.ts +0 -1
  88. package/dist/types/src/components/index.d.ts.map +1 -1
  89. package/dist/types/src/containers/ChessArticle/ChessArticle.d.ts +5 -0
  90. package/dist/types/src/containers/ChessArticle/ChessArticle.d.ts.map +1 -0
  91. package/dist/types/src/containers/ChessArticle/ChessArticle.stories.d.ts +56 -0
  92. package/dist/types/src/containers/ChessArticle/ChessArticle.stories.d.ts.map +1 -0
  93. package/dist/types/src/containers/ChessArticle/index.d.ts +2 -0
  94. package/dist/types/src/containers/ChessArticle/index.d.ts.map +1 -0
  95. package/dist/types/src/containers/ChessCard/ChessCard.d.ts +5 -0
  96. package/dist/types/src/containers/ChessCard/ChessCard.d.ts.map +1 -0
  97. package/dist/types/src/{components/Card.stories.d.ts → containers/ChessCard/ChessCard.stories.d.ts} +2 -9
  98. package/dist/types/src/containers/ChessCard/ChessCard.stories.d.ts.map +1 -0
  99. package/dist/types/src/containers/ChessCard/index.d.ts +2 -0
  100. package/dist/types/src/containers/ChessCard/index.d.ts.map +1 -0
  101. package/dist/types/src/containers/index.d.ts +4 -0
  102. package/dist/types/src/containers/index.d.ts.map +1 -0
  103. package/dist/types/src/index.d.ts +1 -4
  104. package/dist/types/src/index.d.ts.map +1 -1
  105. package/dist/types/src/meta.d.ts +2 -2
  106. package/dist/types/src/meta.d.ts.map +1 -1
  107. package/dist/types/src/operations/index.d.ts +3 -0
  108. package/dist/types/src/operations/index.d.ts.map +1 -0
  109. package/dist/types/src/operations/move.d.ts +5 -0
  110. package/dist/types/src/operations/move.d.ts.map +1 -0
  111. package/dist/types/src/operations/play.d.ts +5 -0
  112. package/dist/types/src/operations/play.d.ts.map +1 -0
  113. package/dist/types/src/operations/print.d.ts +5 -0
  114. package/dist/types/src/operations/print.d.ts.map +1 -0
  115. package/dist/types/src/plugin.d.ts +4 -0
  116. package/dist/types/src/plugin.d.ts.map +1 -0
  117. package/dist/types/src/testing.d.ts +2 -0
  118. package/dist/types/src/testing.d.ts.map +1 -0
  119. package/dist/types/src/translations.d.ts +32 -22
  120. package/dist/types/src/translations.d.ts.map +1 -1
  121. package/dist/types/src/types/Chess.d.ts +12 -22
  122. package/dist/types/src/types/Chess.d.ts.map +1 -1
  123. package/dist/types/src/types/ChessOperation.d.ts +29 -0
  124. package/dist/types/src/types/ChessOperation.d.ts.map +1 -0
  125. package/dist/types/src/types/index.d.ts +1 -1
  126. package/dist/types/src/types/index.d.ts.map +1 -1
  127. package/dist/types/tsconfig.tsbuildinfo +1 -1
  128. package/package.json +117 -58
  129. package/src/ChessPlugin.node.ts +23 -0
  130. package/src/ChessPlugin.test.ts +50 -0
  131. package/src/ChessPlugin.tsx +19 -54
  132. package/src/ChessPlugin.workerd.ts +18 -0
  133. package/src/blueprints/chess-blueprint.ts +32 -0
  134. package/src/blueprints/index.ts +5 -0
  135. package/src/capabilities/blueprint-definition.ts +11 -28
  136. package/src/capabilities/game-variant.ts +43 -0
  137. package/src/capabilities/index.ts +9 -6
  138. package/src/capabilities/operation-handler.ts +14 -0
  139. package/src/components/Chessboard/Chessboard.stories.tsx +94 -0
  140. package/src/components/{Chessboard.tsx → Chessboard/Chessboard.tsx} +34 -31
  141. package/src/components/{Info.tsx → Chessboard/Info.tsx} +34 -77
  142. package/src/components/Chessboard/index.ts +5 -0
  143. package/src/components/Chessboard/types.ts +18 -0
  144. package/src/components/index.ts +1 -5
  145. package/src/containers/ChessArticle/ChessArticle.stories.tsx +59 -0
  146. package/src/containers/ChessArticle/ChessArticle.tsx +108 -0
  147. package/src/containers/ChessArticle/index.ts +5 -0
  148. package/src/containers/ChessCard/ChessCard.stories.tsx +47 -0
  149. package/src/containers/ChessCard/ChessCard.tsx +27 -0
  150. package/src/containers/ChessCard/index.ts +5 -0
  151. package/src/containers/index.ts +8 -0
  152. package/src/index.ts +1 -5
  153. package/src/meta.ts +12 -4
  154. package/src/operations/index.ts +11 -0
  155. package/src/operations/move.ts +36 -0
  156. package/src/operations/play.ts +45 -0
  157. package/src/operations/print.ts +28 -0
  158. package/src/plugin.ts +11 -0
  159. package/src/testing.ts +7 -0
  160. package/src/translations.ts +19 -20
  161. package/src/types/Chess.ts +29 -39
  162. package/src/types/ChessOperation.ts +88 -0
  163. package/src/types/index.ts +1 -1
  164. package/dist/lib/browser/ChessboardContainer-OX6TILWD.mjs +0 -96
  165. package/dist/lib/browser/ChessboardContainer-OX6TILWD.mjs.map +0 -7
  166. package/dist/lib/browser/blueprint-definition-UVRFUJYE.mjs +0 -11
  167. package/dist/lib/browser/chunk-E5NQWXQ7.mjs +0 -306
  168. package/dist/lib/browser/chunk-E5NQWXQ7.mjs.map +0 -7
  169. package/dist/lib/browser/chunk-KQOVCG7Z.mjs +0 -8
  170. package/dist/lib/browser/chunk-KQOVCG7Z.mjs.map +0 -7
  171. package/dist/lib/browser/chunk-M63HYNXU.mjs +0 -84
  172. package/dist/lib/browser/chunk-M63HYNXU.mjs.map +0 -7
  173. package/dist/lib/browser/chunk-VA5TW6SD.mjs +0 -169
  174. package/dist/lib/browser/chunk-VA5TW6SD.mjs.map +0 -7
  175. package/dist/lib/browser/chunk-WSUUPPDK.mjs +0 -26
  176. package/dist/lib/browser/chunk-WSUUPPDK.mjs.map +0 -7
  177. package/dist/lib/browser/index.mjs +0 -124
  178. package/dist/lib/browser/index.mjs.map +0 -7
  179. package/dist/lib/browser/intent-resolver-OQPSVFOB.mjs +0 -25
  180. package/dist/lib/browser/intent-resolver-OQPSVFOB.mjs.map +0 -7
  181. package/dist/lib/browser/meta.json +0 -1
  182. package/dist/lib/browser/react-surface-OLAZCU2E.mjs +0 -38
  183. package/dist/lib/browser/react-surface-OLAZCU2E.mjs.map +0 -7
  184. package/dist/lib/browser/types/index.mjs +0 -10
  185. package/dist/lib/node-esm/ChessboardContainer-PGDHBG6F.mjs +0 -97
  186. package/dist/lib/node-esm/ChessboardContainer-PGDHBG6F.mjs.map +0 -7
  187. package/dist/lib/node-esm/blueprint-definition-Z47C4EEX.mjs +0 -12
  188. package/dist/lib/node-esm/chunk-5C2OYL2B.mjs +0 -27
  189. package/dist/lib/node-esm/chunk-5C2OYL2B.mjs.map +0 -7
  190. package/dist/lib/node-esm/chunk-7YK5RFHY.mjs +0 -85
  191. package/dist/lib/node-esm/chunk-7YK5RFHY.mjs.map +0 -7
  192. package/dist/lib/node-esm/chunk-C55L2OCJ.mjs +0 -10
  193. package/dist/lib/node-esm/chunk-C55L2OCJ.mjs.map +0 -7
  194. package/dist/lib/node-esm/chunk-DWGG7IGH.mjs +0 -307
  195. package/dist/lib/node-esm/chunk-DWGG7IGH.mjs.map +0 -7
  196. package/dist/lib/node-esm/chunk-SW5N5XNV.mjs +0 -170
  197. package/dist/lib/node-esm/chunk-SW5N5XNV.mjs.map +0 -7
  198. package/dist/lib/node-esm/index.mjs +0 -125
  199. package/dist/lib/node-esm/index.mjs.map +0 -7
  200. package/dist/lib/node-esm/intent-resolver-KVGTUDXJ.mjs +0 -26
  201. package/dist/lib/node-esm/intent-resolver-KVGTUDXJ.mjs.map +0 -7
  202. package/dist/lib/node-esm/meta.json +0 -1
  203. package/dist/lib/node-esm/react-surface-FIKCDZ5L.mjs +0 -39
  204. package/dist/lib/node-esm/react-surface-FIKCDZ5L.mjs.map +0 -7
  205. package/dist/lib/node-esm/types/index.mjs +0 -11
  206. package/dist/types/src/capabilities/artifact-definition.d.ts +0 -11
  207. package/dist/types/src/capabilities/artifact-definition.d.ts.map +0 -1
  208. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  209. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  210. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  211. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  212. package/dist/types/src/components/Card.stories.d.ts.map +0 -1
  213. package/dist/types/src/components/Chessboard.d.ts.map +0 -1
  214. package/dist/types/src/components/Chessboard.stories.d.ts +0 -44
  215. package/dist/types/src/components/Chessboard.stories.d.ts.map +0 -1
  216. package/dist/types/src/components/ChessboardArticle.d.ts +0 -7
  217. package/dist/types/src/components/ChessboardArticle.d.ts.map +0 -1
  218. package/dist/types/src/components/ChessboardContainer.d.ts +0 -9
  219. package/dist/types/src/components/ChessboardContainer.d.ts.map +0 -1
  220. package/dist/types/src/components/Info.d.ts.map +0 -1
  221. package/dist/types/src/functions/create.d.ts +0 -14
  222. package/dist/types/src/functions/create.d.ts.map +0 -1
  223. package/dist/types/src/functions/index.d.ts +0 -4
  224. package/dist/types/src/functions/index.d.ts.map +0 -1
  225. package/dist/types/src/functions/move.d.ts +0 -8
  226. package/dist/types/src/functions/move.d.ts.map +0 -1
  227. package/dist/types/src/functions/play.d.ts +0 -9
  228. package/dist/types/src/functions/play.d.ts.map +0 -1
  229. package/dist/types/src/types/ChessAction.d.ts +0 -24
  230. package/dist/types/src/types/ChessAction.d.ts.map +0 -1
  231. package/src/capabilities/artifact-definition.ts +0 -129
  232. package/src/capabilities/intent-resolver.ts +0 -22
  233. package/src/capabilities/react-surface.tsx +0 -23
  234. package/src/components/Card.stories.tsx +0 -66
  235. package/src/components/Chessboard.stories.tsx +0 -49
  236. package/src/components/ChessboardArticle.tsx +0 -69
  237. package/src/components/ChessboardContainer.tsx +0 -44
  238. package/src/functions/create.ts +0 -32
  239. package/src/functions/index.ts +0 -7
  240. package/src/functions/move.ts +0 -45
  241. package/src/functions/play.ts +0 -55
  242. package/src/types/ChessAction.ts +0 -19
  243. /package/dist/lib/{browser/blueprint-definition-UVRFUJYE.mjs.map → neutral/ChessPlugin.mjs.map} +0 -0
  244. /package/dist/lib/{browser/types → neutral/blueprints}/index.mjs.map +0 -0
  245. /package/dist/lib/{node-esm/blueprint-definition-Z47C4EEX.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  246. /package/dist/lib/{node-esm/types → neutral}/index.mjs.map +0 -0
@@ -2,10 +2,12 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { RegistryContext } from '@effect-atom/atom-react';
5
6
  import React, {
6
7
  type PropsWithChildren,
7
8
  forwardRef,
8
9
  useCallback,
10
+ useContext,
9
11
  useEffect,
10
12
  useImperativeHandle,
11
13
  useMemo,
@@ -13,9 +15,10 @@ import React, {
13
15
  } from 'react';
14
16
 
15
17
  import { addEventListener } from '@dxos/async';
18
+ import { useObject } from '@dxos/echo-react';
16
19
  import { type ThemedClassName } from '@dxos/react-ui';
17
20
  import {
18
- ChessModel,
21
+ type ChessModel,
19
22
  Gameboard,
20
23
  type GameboardRootProps,
21
24
  Chessboard as NaturalChessboard,
@@ -24,33 +27,29 @@ import {
24
27
  useGameboardContext,
25
28
  } from '@dxos/react-ui-gameboard';
26
29
  import { useSoundEffect } from '@dxos/react-ui-sfx';
27
- import { mx } from '@dxos/react-ui-theme';
28
30
 
29
- import { type Chess } from '../types';
31
+ import { type Chess } from '#types';
30
32
 
31
33
  import { Info, type InfoProps } from './Info';
34
+ import { ExtendedChessModel } from './types';
32
35
 
33
36
  export interface ChessboardController {
34
37
  setMoveNumber(index: number): void;
35
38
  }
36
39
 
37
- export class ExtendedChessModel extends ChessModel {
38
- constructor(readonly object: Chess.Game) {
39
- super();
40
- }
41
- }
42
-
43
40
  //
44
41
  // Root
45
42
  //
46
43
 
47
44
  type RootProps = PropsWithChildren<{
48
- game: Chess.Game;
45
+ state: Chess.State;
49
46
  }>;
50
47
 
51
- const Root = forwardRef<ChessboardController, RootProps>(({ game, children }, forwardedRef) => {
52
- const model = useMemo(() => new ExtendedChessModel(game), []);
48
+ const Root = forwardRef<ChessboardController, RootProps>(({ state, children }, forwardedRef) => {
49
+ const registry = useContext(RegistryContext);
50
+ const model = useMemo(() => new ExtendedChessModel(registry, state), [registry]);
53
51
  const click = useSoundEffect('Click');
52
+ const [pgn, updatePgn] = useObject(state, 'pgn');
54
53
 
55
54
  // Controller.
56
55
  useImperativeHandle(forwardedRef, () => {
@@ -59,17 +58,21 @@ const Root = forwardRef<ChessboardController, RootProps>(({ game, children }, fo
59
58
  };
60
59
  }, [model]);
61
60
 
61
+ // TODO(wittjosiah): Rather than useEffect to sync ECHO object to model, create an alternative model implementation.
62
+ // This model would take the ECHO object and derive game state directly from that.
63
+ // Mutation methods would mutate the ECHO object and other state would be derived from that.
64
+
62
65
  // External change.
63
66
  // NOTE: Warning if user has not interacted with the board.
64
67
  useEffect(() => {
65
- if (model.pgn !== getRawPgn(game.pgn ?? '')) {
68
+ if (model.pgn !== getRawPgn(pgn ?? '')) {
66
69
  const silent = model.pgn === '*';
67
- model.update(game.pgn);
70
+ model.update(pgn);
68
71
  if (!silent) {
69
72
  void click.play();
70
73
  }
71
74
  }
72
- }, [game.pgn]);
75
+ }, [pgn]);
73
76
 
74
77
  // Move.
75
78
  const handleDrop = useCallback<NonNullable<GameboardRootProps<ChessModel>['onDrop']>>(
@@ -79,7 +82,7 @@ const Root = forwardRef<ChessboardController, RootProps>(({ game, children }, fo
79
82
  }
80
83
 
81
84
  void click.play();
82
- game.pgn = model.pgn;
85
+ updatePgn(() => model.pgn);
83
86
  return true;
84
87
  },
85
88
  [model],
@@ -96,17 +99,13 @@ const Root = forwardRef<ChessboardController, RootProps>(({ game, children }, fo
96
99
  // Content
97
100
  //
98
101
 
99
- type Role = 'card--popover' | 'card--intrinsic' | 'card--extrinsic';
102
+ type Role = 'card--content';
100
103
 
101
104
  type ContentProps = ThemedClassName<PropsWithChildren<{ role?: Role }>>;
102
105
 
103
- const Content = ({ classNames, children, role }: ContentProps) => {
106
+ const Content = ({ classNames, children }: ContentProps) => {
104
107
  return (
105
- <Gameboard.Content
106
- classNames={mx(classNames, role === 'card--popover' && 'size-container popover-square')}
107
- grow={!role || role === 'card--extrinsic'}
108
- contain={!role || role === 'card--extrinsic' || role === 'card--popover'}
109
- >
108
+ <Gameboard.Content grow contain classNames={classNames}>
110
109
  {children}
111
110
  </Gameboard.Content>
112
111
  );
@@ -116,10 +115,13 @@ const Content = ({ classNames, children, role }: ContentProps) => {
116
115
  // Board
117
116
  //
118
117
 
118
+ const BOARD_NAME = 'Chessboard.Board';
119
+
119
120
  type BoardProps = NaturalChessboardProps;
120
121
 
121
122
  const Board = (props: BoardProps) => {
122
- const { model } = useGameboardContext<ChessModel>(Board.displayName);
123
+ const registry = useContext(RegistryContext);
124
+ const { model } = useGameboardContext<ChessModel>(BOARD_NAME);
123
125
 
124
126
  // Keyboard navigation.
125
127
  const ref = useRef<HTMLDivElement>(null);
@@ -132,6 +134,7 @@ const Board = (props: BoardProps) => {
132
134
  ref.current.setAttribute('data-arrow-keys', 'all');
133
135
 
134
136
  return addEventListener(ref.current, 'keydown', (ev) => {
137
+ const moveIndex = registry.get(model.moveIndex);
135
138
  switch (ev.key) {
136
139
  case 'ArrowUp':
137
140
  model.setMoveIndex(0);
@@ -140,23 +143,23 @@ const Board = (props: BoardProps) => {
140
143
  model.setMoveIndex(model.game.history().length);
141
144
  break;
142
145
  case 'ArrowLeft':
143
- if (model.moveIndex.value > 0) {
144
- model.setMoveIndex(model.moveIndex.value - 1);
146
+ if (moveIndex > 0) {
147
+ model.setMoveIndex(moveIndex - 1);
145
148
  }
146
149
  break;
147
150
  case 'ArrowRight':
148
- if (model.moveIndex.value < model.game.history().length) {
149
- model.setMoveIndex(model.moveIndex.value + 1);
151
+ if (moveIndex < model.game.history().length) {
152
+ model.setMoveIndex(moveIndex + 1);
150
153
  }
151
154
  break;
152
155
  }
153
156
  });
154
- }, [model]);
157
+ }, [registry, model]);
155
158
 
156
- return <NaturalChessboard ref={ref} {...props} />;
159
+ return <NaturalChessboard {...props} ref={ref} />;
157
160
  };
158
161
 
159
- Board.displayName = 'Chessboard.Board';
162
+ Board.displayName = BOARD_NAME;
160
163
 
161
164
  //
162
165
  // Chessboard
@@ -2,17 +2,18 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { useAtomValue } from '@effect-atom/atom-react';
5
6
  import React, { type JSX, type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
6
7
 
7
- import { generateName } from '@dxos/display-name';
8
- import { type SpaceMember, getSpace, useMembers } from '@dxos/react-client/echo';
9
- import { Icon, IconButton, Select, type ThemedClassName, useTranslation } from '@dxos/react-ui';
8
+ import { Icon, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
10
9
  import { type Player, useGameboardContext } from '@dxos/react-ui-gameboard';
11
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
12
11
 
13
- import { meta } from '../meta';
12
+ import { meta } from '#meta';
14
13
 
15
- import { type ExtendedChessModel } from './Chessboard';
14
+ import { type ExtendedChessModel } from './types';
15
+
16
+ const INFO_NAME = 'Chessboard.Info';
16
17
 
17
18
  export type InfoProps = ThemedClassName<
18
19
  {
@@ -24,13 +25,12 @@ export type InfoProps = ThemedClassName<
24
25
 
25
26
  export const Info = ({ classNames, orientation = 'white', onOrientationChange, onClose, ...props }: InfoProps) => {
26
27
  const { t } = useTranslation(meta.id);
27
- const { model } = useGameboardContext<ExtendedChessModel>(Info.displayName);
28
- const members = useMembers(getSpace(model.object)?.key);
28
+ const { model } = useGameboardContext<ExtendedChessModel>(INFO_NAME);
29
29
 
30
30
  return (
31
31
  <div
32
32
  className={mx(
33
- 'grid grid-rows-[min-content_1fr_min-content] is-full min-is-[18rem] p-2 overflow-hidden bg-groupSurface rounded-sm',
33
+ 'grid grid-rows-[min-content_1fr_min-content] w-full min-w-[18rem] p-2 overflow-hidden bg-group-surface rounded-xs',
34
34
  classNames,
35
35
  )}
36
36
  >
@@ -39,18 +39,17 @@ export const Info = ({ classNames, orientation = 'white', onOrientationChange, o
39
39
  player={orientation === 'white' ? 'black' : 'white'}
40
40
  icon={
41
41
  onClose && (
42
- <IconButton icon='ph--x--regular' iconOnly label={t('close info button')} size={4} onClick={onClose} />
42
+ <IconButton
43
+ variant='ghost'
44
+ icon='ph--x--regular'
45
+ iconOnly
46
+ label={t('close-info.button')}
47
+ size={4}
48
+ onClick={onClose}
49
+ />
43
50
  )
44
51
  }
45
- >
46
- <PlayerSelector
47
- value={model.object.players?.[orientation === 'white' ? 'black' : 'white']}
48
- onValueChange={(value) => {
49
- model.object.players![orientation === 'white' ? 'black' : 'white'] = value;
50
- }}
51
- members={members}
52
- />
53
- </PlayerIndicator>
52
+ />
54
53
 
55
54
  <History model={model} {...props} />
56
55
 
@@ -63,26 +62,18 @@ export const Info = ({ classNames, orientation = 'white', onOrientationChange, o
63
62
  classNames={mx('transition duration-200 ease-linear', orientation === 'white' && 'rotate-180')}
64
63
  icon='ph--arrows-clockwise--regular'
65
64
  iconOnly
66
- label={t('flip board button')}
65
+ label={t('flip-board.button')}
67
66
  size={4}
68
67
  onClick={() => onOrientationChange(orientation === 'white' ? 'black' : 'white')}
69
68
  />
70
69
  )
71
70
  }
72
- >
73
- <PlayerSelector
74
- value={model.object.players?.[orientation]}
75
- onValueChange={(value) => {
76
- model.object.players![orientation] = value;
77
- }}
78
- members={members}
79
- />
80
- </PlayerIndicator>
71
+ />
81
72
  </div>
82
73
  );
83
74
  };
84
75
 
85
- Info.displayName = 'Chessboard.Info';
76
+ Info.displayName = INFO_NAME;
86
77
 
87
78
  //
88
79
  // History
@@ -97,14 +88,15 @@ type HistoryProps = ThemedClassName<{
97
88
 
98
89
  const History = ({ classNames, model, min, max, onSelect }: HistoryProps) => {
99
90
  const { t } = useTranslation(meta.id);
91
+ const moveIndex = useAtomValue(model.moveIndex);
100
92
  const label = model.game.isGameOver()
101
93
  ? model.game.isCheckmate()
102
- ? t('game.checkmate label')
94
+ ? t('game.checkmate.label')
103
95
  : model.game.isStalemate()
104
- ? t('game.stalemate label')
105
- : t('game.draw label')
96
+ ? t('game.stalemate.label')
97
+ : t('game.draw.label')
106
98
  : model.game.isCheck()
107
- ? t('game.check label')
99
+ ? t('game.check.label')
108
100
  : undefined;
109
101
 
110
102
  const history = model.game.history();
@@ -130,28 +122,28 @@ const History = ({ classNames, model, min, max, onSelect }: HistoryProps) => {
130
122
  }, [history.length]);
131
123
 
132
124
  useEffect(() => {
133
- const div = scrollerRef.current?.querySelector(`[data-index="${model.moveIndex.value - 1}"]`);
125
+ const div = scrollerRef.current?.querySelector(`[data-index="${moveIndex - 1}"]`);
134
126
  scrollerRef.current?.classList.add('scrollbar-none');
135
127
  div?.scrollIntoView({ behavior: 'smooth' });
136
128
  scrollerRef.current?.classList.remove('scrollbar-none');
137
- }, [model.moveIndex.value]);
129
+ }, [moveIndex]);
138
130
 
139
131
  return (
140
132
  <div
141
133
  ref={scrollerRef}
142
- className={mx('overflow-y-scroll', classNames)}
134
+ className={mx('overflow-y-auto', classNames)}
143
135
  style={{
144
136
  minHeight: min === undefined ? 'auto' : `${min * 24}px`,
145
137
  maxHeight: max === undefined ? 'auto' : `${max * 24}px`,
146
138
  }}
147
139
  >
148
140
  {moves.map(([a, b], index) => (
149
- <div key={index} className='grid grid-cols-[3rem_1fr_1fr_1rem] gap-2 pis-4 leading-1'>
141
+ <div key={index} className='grid grid-cols-[3rem_1fr_1fr_1rem] gap-2 ps-4'>
150
142
  <div className='content-center text-xs text-subdued'>{index + 1}</div>
151
143
  {a && (
152
144
  <div
153
145
  data-index={a.index}
154
- className={mx('pis-2 cursor-pointer', a.index === model.moveIndex.value - 1 && 'bg-primary-500')}
146
+ className={mx('ps-2 text-sm cursor-pointer', a.index === moveIndex - 1 && 'bg-primary-500')}
155
147
  onClick={() => onSelect?.(a.index + 1)}
156
148
  >
157
149
  {a.move}
@@ -160,7 +152,7 @@ const History = ({ classNames, model, min, max, onSelect }: HistoryProps) => {
160
152
  {b && (
161
153
  <div
162
154
  data-index={b.index}
163
- className={mx('pis-2 cursor-pointer', b.index === model.moveIndex.value - 1 && 'bg-primary-500')}
155
+ className={mx('ps-2 text-sm cursor-pointer', b.index === moveIndex - 1 && 'bg-primary-500')}
164
156
  onClick={() => onSelect?.(b.index + 1)}
165
157
  >
166
158
  {b.move}
@@ -186,12 +178,12 @@ type PlayerIndicatorProps = PropsWithChildren<{
186
178
  const PlayerIndicator = ({ children, model, player, icon }: PlayerIndicatorProps) => {
187
179
  const turn = player === (model.game.turn() === 'w' ? 'white' : 'black');
188
180
  return (
189
- <div className='grid grid-cols-[2rem_1fr_2rem] gap-2 bs-[--rail-size] pis-1 pie-1 flex items-center overflow-hidden'>
181
+ <div className='grid grid-cols-[2rem_1fr_2rem] gap-2 h-(--dx-rail-size) px-1 flex items-center overflow-hidden'>
190
182
  <div className='place-items-center'>
191
183
  <Icon
192
184
  icon={turn ? 'ph--circle--fill' : 'ph--circle--thin'}
193
185
  size={6}
194
- classNames={mx(turn && (model.game.isCheckmate() ? 'text-red-500' : 'text-green-500'))}
186
+ classNames={mx(turn && (model.game.isCheckmate() ? 'text-error-500' : 'text-success-text'))}
195
187
  />
196
188
  </div>
197
189
  <div className='truncate overflow-hidden items-center'>{children}</div>
@@ -199,38 +191,3 @@ const PlayerIndicator = ({ children, model, player, icon }: PlayerIndicatorProps
199
191
  </div>
200
192
  );
201
193
  };
202
-
203
- //
204
- // PlayerSelector
205
- //
206
-
207
- type PlayerSelectorProps = {
208
- value?: string;
209
- onValueChange: (player: string) => void;
210
- members: SpaceMember[];
211
- };
212
-
213
- const PlayerSelector = ({ value, onValueChange, members }: PlayerSelectorProps) => {
214
- const { t } = useTranslation(meta.id);
215
- return (
216
- <Select.Root value={value} onValueChange={onValueChange}>
217
- <Select.TriggerButton placeholder={t('select player button')} />
218
- <Select.Portal>
219
- <Select.Content>
220
- <Select.Viewport>
221
- {members.map((member) => {
222
- const memberKey = member.identity.identityKey.toHex();
223
- const displayName = member.identity?.profile?.displayName || generateName(memberKey);
224
- return (
225
- <Select.Option key={memberKey} value={memberKey}>
226
- {displayName}
227
- </Select.Option>
228
- );
229
- })}
230
- </Select.Viewport>
231
- <Select.Arrow />
232
- </Select.Content>
233
- </Select.Portal>
234
- </Select.Root>
235
- );
236
- };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Chessboard';
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Registry } from '@effect-atom/atom-react';
6
+
7
+ import { ChessModel } from '@dxos/react-ui-gameboard';
8
+
9
+ import { type Chess } from '#types';
10
+
11
+ export class ExtendedChessModel extends ChessModel {
12
+ constructor(
13
+ registry: Registry.Registry,
14
+ readonly object: Chess.State,
15
+ ) {
16
+ super(registry);
17
+ }
18
+ }
@@ -1,9 +1,5 @@
1
1
  //
2
- // Copyright 2023 DXOS.org
2
+ // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- import { lazy } from 'react';
6
-
7
5
  export * from './Chessboard';
8
-
9
- export const ChessboardContainer = lazy(() => import('./ChessboardContainer'));
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { type Obj } from '@dxos/echo';
9
+ import { make as makeGame } from '@dxos/plugin-game';
10
+ import { withClientProvider } from '@dxos/react-client/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
+
13
+ import { translations } from '#translations';
14
+ import { Chess } from '#types';
15
+
16
+ import { ChessArticle } from './ChessArticle';
17
+
18
+ type DefaultStoryProps = {
19
+ pgn?: string;
20
+ };
21
+
22
+ const DefaultStory = ({ pgn }: DefaultStoryProps) => {
23
+ const { game, state } = useMemo(() => {
24
+ const state = Chess.make(pgn ? { pgn } : undefined);
25
+ const game = makeGame({ name: 'Story game', variant: state as Obj.Unknown });
26
+ return { game, state };
27
+ }, [pgn]);
28
+ return <ChessArticle role='article' attendableId='story' game={game} variant={state} />;
29
+ };
30
+
31
+ const meta = {
32
+ title: 'plugins/plugin-chess/containers/ChessArticle',
33
+ component: DefaultStory,
34
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withClientProvider({ createIdentity: true })],
35
+ parameters: {
36
+ layout: 'fullscreen',
37
+ translations,
38
+ },
39
+ } satisfies Meta<typeof DefaultStory>;
40
+
41
+ export default meta;
42
+
43
+ type Story = StoryObj<typeof meta>;
44
+
45
+ export const Default: Story = {
46
+ args: {},
47
+ };
48
+
49
+ export const EndGame: Story = {
50
+ args: {
51
+ pgn: '1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. c3 Nf6 5. d4 exd4 6. cxd4 Bb4+ 7. Nc3 d5 8. exd5 Nxd5 9. O-O Be6 10. Qb3 Na5 11. Qa4+ c6 12. Bxd5 Bxc3 13. Bxe6 fxe6 *',
52
+ },
53
+ };
54
+
55
+ export const Promption: Story = {
56
+ args: {
57
+ pgn: '1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. c3 Nf6 5. d4 exd4 6. cxd4 Bb4+ 7. Nc3 d5 8. exd5 Nxd5 9. O-O Be6 10. Qb3 Na5 11. Qa4+ c6 12. Bxd5 Bxc3 13. Bxe6 fxe6 14. d5 Qg5 15. dxe6 Qg4 16. e7 Kf7 17. bxc3 Kg6 *',
58
+ },
59
+ };
@@ -0,0 +1,108 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Chess as ChessJS } from 'chess.js';
6
+ import React, { useCallback, useRef, useState } from 'react';
7
+
8
+ import { Obj } from '@dxos/echo';
9
+ import { type GameVariantSurfaceProps } from '@dxos/plugin-game';
10
+ import { Panel, Toolbar, useTranslation } from '@dxos/react-ui';
11
+ import { type Player } from '@dxos/react-ui-gameboard';
12
+ import { mx } from '@dxos/ui-theme';
13
+
14
+ import { Chessboard, type ChessboardController, type ChessboardInfoProps } from '#components';
15
+ import { meta } from '#meta';
16
+ import { Chess } from '#types';
17
+
18
+ export type ChessArticleProps = GameVariantSurfaceProps;
19
+
20
+ export const ChessArticle = ({ role, variant }: ChessArticleProps) => {
21
+ const { t } = useTranslation(meta.id);
22
+ const [orientation, setOrientation] = useState<Player>('white');
23
+ const [showInfo, setShowInfo] = useState(true);
24
+ const controller = useRef<ChessboardController>(null);
25
+
26
+ const handleSelect = useCallback<NonNullable<ChessboardInfoProps['onSelect']>>((index) => {
27
+ controller.current?.setMoveNumber(index);
28
+ }, []);
29
+
30
+ const isGameOver = (() => {
31
+ if (!Obj.instanceOf(Chess.State, variant) || (!variant.pgn && !variant.fen)) {
32
+ return false;
33
+ }
34
+ try {
35
+ const chess = new ChessJS();
36
+ if (variant.pgn) {
37
+ chess.loadPgn(variant.pgn);
38
+ } else if (variant.fen) {
39
+ chess.load(variant.fen);
40
+ }
41
+ return chess.isGameOver();
42
+ } catch {
43
+ return false;
44
+ }
45
+ })();
46
+
47
+ const handleNewGame = useCallback(() => {
48
+ if (!Obj.instanceOf(Chess.State, variant)) {
49
+ return;
50
+ }
51
+ Obj.update(variant, (variant) => {
52
+ const mutable = variant as Obj.Mutable<typeof variant>;
53
+ mutable.pgn = undefined;
54
+ mutable.fen = undefined;
55
+ });
56
+ }, [variant]);
57
+
58
+ if (!Obj.instanceOf(Chess.State, variant)) {
59
+ return null;
60
+ }
61
+
62
+ return (
63
+ <Chessboard.Root state={variant} ref={controller}>
64
+ <Panel.Root role={role} classNames='@container'>
65
+ <Panel.Toolbar asChild>
66
+ <Toolbar.Root>
67
+ {isGameOver && <Toolbar.Button onClick={handleNewGame}>{t('new-game.button')}</Toolbar.Button>}
68
+ <div className='grow' />
69
+ <Toolbar.IconButton
70
+ icon='ph--info--regular'
71
+ iconOnly
72
+ label={t('toggle-info.button')}
73
+ classNames={mx('invisible @4xl:visible')}
74
+ onClick={() => setShowInfo((open) => !open)}
75
+ />
76
+ </Toolbar.Root>
77
+ </Panel.Toolbar>
78
+ <Panel.Content>
79
+ <div
80
+ className={mx(
81
+ 'grid h-full w-full',
82
+ showInfo && '@4xl:grid-cols-[1fr_320px] gap-8',
83
+ role === 'article' && 'p-4',
84
+ role === 'section' && 'aspect-square',
85
+ role === 'section' && showInfo && '@4xl:aspect-auto',
86
+ )}
87
+ >
88
+ <Chessboard.Content>
89
+ <Chessboard.Board classNames='border rounded-xs' orientation={orientation} />
90
+ </Chessboard.Content>
91
+ {showInfo && (
92
+ <div className='hidden @4xl:flex flex-col justify-center items-center overflow-hidden'>
93
+ <Chessboard.Info
94
+ orientation={orientation}
95
+ min={8}
96
+ max={8}
97
+ onOrientationChange={setOrientation}
98
+ onClose={() => setShowInfo(false)}
99
+ onSelect={handleSelect}
100
+ />
101
+ </div>
102
+ )}
103
+ </div>
104
+ </Panel.Content>
105
+ </Panel.Root>
106
+ </Chessboard.Root>
107
+ );
108
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export { ChessArticle as default } from './ChessArticle';
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { make as makeGame } from '@dxos/plugin-game';
9
+ import { CardContainer } from '@dxos/react-ui-mosaic/testing';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+
12
+ import { meta as pluginMeta } from '#meta';
13
+ import { Chess } from '#types';
14
+
15
+ import { ChessCard } from './ChessCard';
16
+
17
+ const SAMPLE_PGN =
18
+ '1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. c3 Nf6 5. d4 exd4 6. cxd4 Bb4+ 7. Nc3 d5 8. exd5 Nxd5 9. O-O Be6 10. Qb3 Na5 11. Qa4+ c6 12. Bxd5 Bxc3 13. Bxe6 fxe6 14. d5 Qg5 15. dxe6 Qg4 16. e7 Kf7 17. bxc3 Kg6 *';
19
+
20
+ const CardStory = () => {
21
+ const { game, state } = useMemo(() => {
22
+ const state = Chess.make({ pgn: SAMPLE_PGN });
23
+ const game = makeGame({ name: 'Story game', variant: state });
24
+ return { game, state };
25
+ }, []);
26
+ return (
27
+ <CardContainer role='popover' icon={pluginMeta.icon}>
28
+ <ChessCard role='card--content' game={game} variant={state} />
29
+ </CardContainer>
30
+ );
31
+ };
32
+
33
+ const meta = {
34
+ title: 'plugins/plugin-chess/containers/ChessCard',
35
+ render: () => <CardStory />,
36
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
37
+ parameters: {
38
+ layout: 'fullscreen',
39
+ },
40
+ tags: ['cards'],
41
+ } satisfies Meta;
42
+
43
+ export default meta;
44
+
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ export const Popover: Story = {};
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Obj } from '@dxos/echo';
8
+ import { type GameVariantSurfaceProps } from '@dxos/plugin-game';
9
+
10
+ import { Chessboard } from '#components';
11
+ import { Chess } from '#types';
12
+
13
+ export type ChessCardProps = GameVariantSurfaceProps;
14
+
15
+ export const ChessCard = ({ variant }: ChessCardProps) => {
16
+ if (!Obj.instanceOf(Chess.State, variant)) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <Chessboard.Root state={variant}>
22
+ <Chessboard.Content>
23
+ <Chessboard.Board />
24
+ </Chessboard.Content>
25
+ </Chessboard.Root>
26
+ );
27
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export { ChessCard as default } from './ChessCard';