@dxos/plugin-chess 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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 (254) 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-JHI5OXYR.mjs +88 -0
  5. package/dist/lib/neutral/ChessArticle-JHI5OXYR.mjs.map +7 -0
  6. package/dist/lib/neutral/ChessCard-4TUTIFB6.mjs +19 -0
  7. package/dist/lib/neutral/ChessCard-4TUTIFB6.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-4MOWVVWD.mjs +15 -0
  14. package/dist/lib/neutral/blueprint-definition-4MOWVVWD.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-7XZ4CRDL.mjs +32 -0
  19. package/dist/lib/neutral/chunk-7XZ4CRDL.mjs.map +7 -0
  20. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  21. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  22. package/dist/lib/neutral/chunk-OYWDKJ5B.mjs +148 -0
  23. package/dist/lib/neutral/chunk-OYWDKJ5B.mjs.map +7 -0
  24. package/dist/lib/neutral/chunk-PJOAFP6S.mjs +41 -0
  25. package/dist/lib/neutral/chunk-PJOAFP6S.mjs.map +7 -0
  26. package/dist/lib/neutral/chunk-XWKNXG62.mjs +34 -0
  27. package/dist/lib/neutral/chunk-XWKNXG62.mjs.map +7 -0
  28. package/dist/lib/neutral/chunk-YB6WZOZI.mjs +8 -0
  29. package/dist/lib/neutral/chunk-YB6WZOZI.mjs.map +7 -0
  30. package/dist/lib/neutral/components/index.mjs +239 -0
  31. package/dist/lib/neutral/components/index.mjs.map +7 -0
  32. package/dist/lib/neutral/containers/index.mjs +11 -0
  33. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  34. package/dist/lib/neutral/game-variant-ZLPEN2QK.mjs +41 -0
  35. package/dist/lib/neutral/game-variant-ZLPEN2QK.mjs.map +7 -0
  36. package/dist/lib/neutral/index.mjs +18 -0
  37. package/dist/lib/neutral/index.mjs.map +7 -0
  38. package/dist/lib/neutral/meta.json +1 -0
  39. package/dist/lib/neutral/meta.mjs +8 -0
  40. package/dist/lib/neutral/meta.mjs.map +7 -0
  41. package/dist/lib/neutral/move-6NCMKKWD.mjs +37 -0
  42. package/dist/lib/neutral/move-6NCMKKWD.mjs.map +7 -0
  43. package/dist/lib/neutral/operation-handler-EKZKFQUG.mjs +13 -0
  44. package/dist/lib/neutral/operation-handler-EKZKFQUG.mjs.map +7 -0
  45. package/dist/lib/neutral/operations/index.mjs +8 -0
  46. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  47. package/dist/lib/neutral/play-F5L5LFLW.mjs +46 -0
  48. package/dist/lib/neutral/play-F5L5LFLW.mjs.map +7 -0
  49. package/dist/lib/neutral/plugin.mjs +16 -0
  50. package/dist/lib/neutral/plugin.mjs.map +7 -0
  51. package/dist/lib/neutral/print-GMSNKAS2.mjs +29 -0
  52. package/dist/lib/neutral/print-GMSNKAS2.mjs.map +7 -0
  53. package/dist/lib/neutral/testing.mjs +8 -0
  54. package/dist/lib/neutral/testing.mjs.map +7 -0
  55. package/dist/lib/neutral/translations.mjs +39 -0
  56. package/dist/lib/neutral/translations.mjs.map +7 -0
  57. package/dist/lib/neutral/types/index.mjs +10 -0
  58. package/dist/lib/neutral/types/index.mjs.map +7 -0
  59. package/dist/types/src/ChessPlugin.d.ts +3 -1
  60. package/dist/types/src/ChessPlugin.d.ts.map +1 -1
  61. package/dist/types/src/ChessPlugin.node.d.ts +8 -0
  62. package/dist/types/src/ChessPlugin.node.d.ts.map +1 -0
  63. package/dist/types/src/ChessPlugin.test.d.ts +2 -0
  64. package/dist/types/src/ChessPlugin.test.d.ts.map +1 -0
  65. package/dist/types/src/ChessPlugin.workerd.d.ts +4 -0
  66. package/dist/types/src/ChessPlugin.workerd.d.ts.map +1 -0
  67. package/dist/types/src/blueprints/chess-blueprint.d.ts +4 -0
  68. package/dist/types/src/blueprints/chess-blueprint.d.ts.map +1 -0
  69. package/dist/types/src/blueprints/index.d.ts +2 -0
  70. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  71. package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -2
  72. package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/game-variant.d.ts +6 -0
  74. package/dist/types/src/capabilities/game-variant.d.ts.map +1 -0
  75. package/dist/types/src/capabilities/index.d.ts +6 -3
  76. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  77. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  78. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  79. package/dist/types/src/components/Chessboard/Chessboard.d.ts +38 -0
  80. package/dist/types/src/components/Chessboard/Chessboard.d.ts.map +1 -0
  81. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts +65 -0
  82. package/dist/types/src/components/Chessboard/Chessboard.stories.d.ts.map +1 -0
  83. package/dist/types/src/components/Chessboard/Info.d.ts +20 -0
  84. package/dist/types/src/components/Chessboard/Info.d.ts.map +1 -0
  85. package/dist/types/src/components/Chessboard/index.d.ts +2 -0
  86. package/dist/types/src/components/Chessboard/index.d.ts.map +1 -0
  87. package/dist/types/src/components/Chessboard/types.d.ts +8 -0
  88. package/dist/types/src/components/Chessboard/types.d.ts.map +1 -0
  89. package/dist/types/src/components/index.d.ts +0 -1
  90. package/dist/types/src/components/index.d.ts.map +1 -1
  91. package/dist/types/src/containers/ChessArticle/ChessArticle.d.ts +5 -0
  92. package/dist/types/src/containers/ChessArticle/ChessArticle.d.ts.map +1 -0
  93. package/dist/types/src/containers/ChessArticle/ChessArticle.stories.d.ts +64 -0
  94. package/dist/types/src/containers/ChessArticle/ChessArticle.stories.d.ts.map +1 -0
  95. package/dist/types/src/containers/ChessArticle/index.d.ts +2 -0
  96. package/dist/types/src/containers/ChessArticle/index.d.ts.map +1 -0
  97. package/dist/types/src/containers/ChessCard/ChessCard.d.ts +5 -0
  98. package/dist/types/src/containers/ChessCard/ChessCard.d.ts.map +1 -0
  99. package/dist/types/src/containers/ChessCard/ChessCard.stories.d.ts +15 -0
  100. package/dist/types/src/containers/ChessCard/ChessCard.stories.d.ts.map +1 -0
  101. package/dist/types/src/containers/ChessCard/index.d.ts +2 -0
  102. package/dist/types/src/containers/ChessCard/index.d.ts.map +1 -0
  103. package/dist/types/src/containers/index.d.ts +4 -0
  104. package/dist/types/src/containers/index.d.ts.map +1 -0
  105. package/dist/types/src/index.d.ts +1 -4
  106. package/dist/types/src/index.d.ts.map +1 -1
  107. package/dist/types/src/meta.d.ts +2 -3
  108. package/dist/types/src/meta.d.ts.map +1 -1
  109. package/dist/types/src/operations/index.d.ts +3 -0
  110. package/dist/types/src/operations/index.d.ts.map +1 -0
  111. package/dist/types/src/operations/move.d.ts +5 -0
  112. package/dist/types/src/operations/move.d.ts.map +1 -0
  113. package/dist/types/src/operations/play.d.ts +5 -0
  114. package/dist/types/src/operations/play.d.ts.map +1 -0
  115. package/dist/types/src/operations/print.d.ts +5 -0
  116. package/dist/types/src/operations/print.d.ts.map +1 -0
  117. package/dist/types/src/plugin.d.ts +4 -0
  118. package/dist/types/src/plugin.d.ts.map +1 -0
  119. package/dist/types/src/testing.d.ts +2 -0
  120. package/dist/types/src/testing.d.ts.map +1 -0
  121. package/dist/types/src/translations.d.ts +40 -20
  122. package/dist/types/src/translations.d.ts.map +1 -1
  123. package/dist/types/src/types/Chess.d.ts +14 -21
  124. package/dist/types/src/types/Chess.d.ts.map +1 -1
  125. package/dist/types/src/types/ChessOperation.d.ts +29 -0
  126. package/dist/types/src/types/ChessOperation.d.ts.map +1 -0
  127. package/dist/types/src/types/index.d.ts +1 -1
  128. package/dist/types/src/types/index.d.ts.map +1 -1
  129. package/dist/types/tsconfig.tsbuildinfo +1 -1
  130. package/package.json +116 -49
  131. package/src/ChessPlugin.node.ts +23 -0
  132. package/src/ChessPlugin.test.ts +50 -0
  133. package/src/ChessPlugin.tsx +26 -54
  134. package/src/ChessPlugin.workerd.ts +18 -0
  135. package/src/blueprints/chess-blueprint.ts +32 -0
  136. package/src/blueprints/index.ts +5 -0
  137. package/src/capabilities/blueprint-definition.ts +12 -27
  138. package/src/capabilities/game-variant.ts +45 -0
  139. package/src/capabilities/index.ts +14 -4
  140. package/src/capabilities/operation-handler.ts +14 -0
  141. package/src/components/Chessboard/Chessboard.stories.tsx +94 -0
  142. package/src/components/Chessboard/Chessboard.tsx +180 -0
  143. package/src/components/Chessboard/Info.tsx +193 -0
  144. package/src/components/Chessboard/index.ts +5 -0
  145. package/src/components/Chessboard/types.ts +18 -0
  146. package/src/components/index.ts +1 -5
  147. package/src/containers/ChessArticle/ChessArticle.stories.tsx +59 -0
  148. package/src/containers/ChessArticle/ChessArticle.tsx +108 -0
  149. package/src/containers/ChessArticle/index.ts +5 -0
  150. package/src/containers/ChessCard/ChessCard.stories.tsx +47 -0
  151. package/src/containers/ChessCard/ChessCard.tsx +27 -0
  152. package/src/containers/ChessCard/index.ts +5 -0
  153. package/src/containers/index.ts +8 -0
  154. package/src/index.ts +1 -5
  155. package/src/meta.ts +13 -7
  156. package/src/operations/index.ts +11 -0
  157. package/src/operations/move.ts +36 -0
  158. package/src/operations/play.ts +45 -0
  159. package/src/operations/print.ts +28 -0
  160. package/src/plugin.ts +11 -0
  161. package/src/testing.ts +7 -0
  162. package/src/translations.ts +23 -18
  163. package/src/types/Chess.ts +28 -31
  164. package/src/types/ChessOperation.ts +88 -0
  165. package/src/types/index.ts +1 -1
  166. package/src/vite-env.d.ts +10 -0
  167. package/dist/lib/browser/ChessboardContainer-NNIOQIRP.mjs +0 -90
  168. package/dist/lib/browser/ChessboardContainer-NNIOQIRP.mjs.map +0 -7
  169. package/dist/lib/browser/blueprint-definition-OJQYAZGZ.mjs +0 -38
  170. package/dist/lib/browser/blueprint-definition-OJQYAZGZ.mjs.map +0 -7
  171. package/dist/lib/browser/chunk-2W3S5N6C.mjs +0 -257
  172. package/dist/lib/browser/chunk-2W3S5N6C.mjs.map +0 -7
  173. package/dist/lib/browser/chunk-EJMPHRSR.mjs +0 -94
  174. package/dist/lib/browser/chunk-EJMPHRSR.mjs.map +0 -7
  175. package/dist/lib/browser/chunk-OBJAHY6H.mjs +0 -79
  176. package/dist/lib/browser/chunk-OBJAHY6H.mjs.map +0 -7
  177. package/dist/lib/browser/chunk-PE3IWOFR.mjs +0 -28
  178. package/dist/lib/browser/chunk-PE3IWOFR.mjs.map +0 -7
  179. package/dist/lib/browser/chunk-PPBBLHAC.mjs +0 -8
  180. package/dist/lib/browser/chunk-PPBBLHAC.mjs.map +0 -7
  181. package/dist/lib/browser/index.mjs +0 -116
  182. package/dist/lib/browser/index.mjs.map +0 -7
  183. package/dist/lib/browser/intent-resolver-JWKKRNGG.mjs +0 -25
  184. package/dist/lib/browser/intent-resolver-JWKKRNGG.mjs.map +0 -7
  185. package/dist/lib/browser/meta.json +0 -1
  186. package/dist/lib/browser/react-surface-RIXD6RTS.mjs +0 -38
  187. package/dist/lib/browser/react-surface-RIXD6RTS.mjs.map +0 -7
  188. package/dist/lib/browser/types/index.mjs +0 -10
  189. package/dist/lib/node-esm/ChessboardContainer-FCPUV2T2.mjs +0 -91
  190. package/dist/lib/node-esm/ChessboardContainer-FCPUV2T2.mjs.map +0 -7
  191. package/dist/lib/node-esm/blueprint-definition-F5SGHCDA.mjs +0 -39
  192. package/dist/lib/node-esm/blueprint-definition-F5SGHCDA.mjs.map +0 -7
  193. package/dist/lib/node-esm/chunk-42EKKMOO.mjs +0 -10
  194. package/dist/lib/node-esm/chunk-42EKKMOO.mjs.map +0 -7
  195. package/dist/lib/node-esm/chunk-5APYXPBX.mjs +0 -95
  196. package/dist/lib/node-esm/chunk-5APYXPBX.mjs.map +0 -7
  197. package/dist/lib/node-esm/chunk-6OWCPDUA.mjs +0 -29
  198. package/dist/lib/node-esm/chunk-6OWCPDUA.mjs.map +0 -7
  199. package/dist/lib/node-esm/chunk-LCCUF5X5.mjs +0 -80
  200. package/dist/lib/node-esm/chunk-LCCUF5X5.mjs.map +0 -7
  201. package/dist/lib/node-esm/chunk-QAGOXJGH.mjs +0 -258
  202. package/dist/lib/node-esm/chunk-QAGOXJGH.mjs.map +0 -7
  203. package/dist/lib/node-esm/index.mjs +0 -117
  204. package/dist/lib/node-esm/index.mjs.map +0 -7
  205. package/dist/lib/node-esm/intent-resolver-G6XJAW6C.mjs +0 -26
  206. package/dist/lib/node-esm/intent-resolver-G6XJAW6C.mjs.map +0 -7
  207. package/dist/lib/node-esm/meta.json +0 -1
  208. package/dist/lib/node-esm/react-surface-GGRDWZJT.mjs +0 -39
  209. package/dist/lib/node-esm/react-surface-GGRDWZJT.mjs.map +0 -7
  210. package/dist/lib/node-esm/types/index.mjs +0 -11
  211. package/dist/types/src/capabilities/artifact-definition.d.ts +0 -11
  212. package/dist/types/src/capabilities/artifact-definition.d.ts.map +0 -1
  213. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  214. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  215. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  216. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  217. package/dist/types/src/components/Card.stories.d.ts +0 -49
  218. package/dist/types/src/components/Card.stories.d.ts.map +0 -1
  219. package/dist/types/src/components/Chessboard.d.ts +0 -32
  220. package/dist/types/src/components/Chessboard.d.ts.map +0 -1
  221. package/dist/types/src/components/Chessboard.stories.d.ts +0 -42
  222. package/dist/types/src/components/Chessboard.stories.d.ts.map +0 -1
  223. package/dist/types/src/components/ChessboardArticle.d.ts +0 -6
  224. package/dist/types/src/components/ChessboardArticle.d.ts.map +0 -1
  225. package/dist/types/src/components/ChessboardContainer.d.ts +0 -9
  226. package/dist/types/src/components/ChessboardContainer.d.ts.map +0 -1
  227. package/dist/types/src/components/ChessboardInfo.d.ts +0 -20
  228. package/dist/types/src/components/ChessboardInfo.d.ts.map +0 -1
  229. package/dist/types/src/components/ChessboardPlayers.d.ts +0 -8
  230. package/dist/types/src/components/ChessboardPlayers.d.ts.map +0 -1
  231. package/dist/types/src/functions/index.d.ts +0 -3
  232. package/dist/types/src/functions/index.d.ts.map +0 -1
  233. package/dist/types/src/functions/move.d.ts +0 -6
  234. package/dist/types/src/functions/move.d.ts.map +0 -1
  235. package/dist/types/src/functions/play.d.ts +0 -8
  236. package/dist/types/src/functions/play.d.ts.map +0 -1
  237. package/dist/types/src/types/ChessAction.d.ts +0 -23
  238. package/dist/types/src/types/ChessAction.d.ts.map +0 -1
  239. package/src/capabilities/artifact-definition.ts +0 -128
  240. package/src/capabilities/intent-resolver.ts +0 -22
  241. package/src/capabilities/react-surface.tsx +0 -23
  242. package/src/components/Card.stories.tsx +0 -68
  243. package/src/components/Chessboard.stories.tsx +0 -50
  244. package/src/components/Chessboard.tsx +0 -101
  245. package/src/components/ChessboardArticle.tsx +0 -57
  246. package/src/components/ChessboardContainer.tsx +0 -44
  247. package/src/components/ChessboardInfo.tsx +0 -140
  248. package/src/components/ChessboardPlayers.tsx +0 -97
  249. package/src/functions/index.ts +0 -6
  250. package/src/functions/move.ts +0 -43
  251. package/src/functions/play.ts +0 -45
  252. package/src/types/ChessAction.ts +0 -19
  253. /package/dist/lib/{browser/types/index.mjs.map → neutral/ChessPlugin.mjs.map} +0 -0
  254. /package/dist/lib/{node-esm/types → neutral/blueprints}/index.mjs.map +0 -0
@@ -0,0 +1,180 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext } from '@effect-atom/atom-react';
6
+ import React, {
7
+ type PropsWithChildren,
8
+ forwardRef,
9
+ useCallback,
10
+ useContext,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useMemo,
14
+ useRef,
15
+ } from 'react';
16
+
17
+ import { addEventListener } from '@dxos/async';
18
+ import { useObject } from '@dxos/echo-react';
19
+ import { type ThemedClassName } from '@dxos/react-ui';
20
+ import { useSoundEffect } from '@dxos/react-ui-audio';
21
+ import {
22
+ type ChessModel,
23
+ Gameboard,
24
+ type GameboardRootProps,
25
+ Chessboard as NaturalChessboard,
26
+ type ChessboardProps as NaturalChessboardProps,
27
+ getRawPgn,
28
+ useGameboardContext,
29
+ } from '@dxos/react-ui-gameboard';
30
+
31
+ import { type Chess } from '#types';
32
+
33
+ import { Info, type InfoProps } from './Info';
34
+ import { ExtendedChessModel } from './types';
35
+
36
+ export interface ChessboardController {
37
+ setMoveNumber(index: number): void;
38
+ }
39
+
40
+ //
41
+ // Root
42
+ //
43
+
44
+ type RootProps = PropsWithChildren<{
45
+ state: Chess.State;
46
+ }>;
47
+
48
+ const Root = forwardRef<ChessboardController, RootProps>(({ state, children }, forwardedRef) => {
49
+ const registry = useContext(RegistryContext);
50
+ const model = useMemo(() => new ExtendedChessModel(registry, state), [registry]);
51
+ const click = useSoundEffect('Click');
52
+ const [pgn, updatePgn] = useObject(state, 'pgn');
53
+
54
+ // Controller.
55
+ useImperativeHandle(forwardedRef, () => {
56
+ return {
57
+ setMoveNumber: (index) => model.setMoveIndex(index),
58
+ };
59
+ }, [model]);
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
+
65
+ // External change.
66
+ // NOTE: Warning if user has not interacted with the board.
67
+ useEffect(() => {
68
+ if (model.pgn !== getRawPgn(pgn ?? '')) {
69
+ const silent = model.pgn === '*';
70
+ model.update(pgn);
71
+ if (!silent) {
72
+ void click.play();
73
+ }
74
+ }
75
+ }, [pgn]);
76
+
77
+ // Move.
78
+ const handleDrop = useCallback<NonNullable<GameboardRootProps<ChessModel>['onDrop']>>(
79
+ (move) => {
80
+ if (!model.makeMove(move)) {
81
+ return false;
82
+ }
83
+
84
+ void click.play();
85
+ updatePgn(() => model.pgn);
86
+ return true;
87
+ },
88
+ [model],
89
+ );
90
+
91
+ return (
92
+ <Gameboard.Root model={model} onDrop={handleDrop}>
93
+ {children}
94
+ </Gameboard.Root>
95
+ );
96
+ });
97
+
98
+ //
99
+ // Content
100
+ //
101
+
102
+ type Role = 'card--content';
103
+
104
+ type ContentProps = ThemedClassName<PropsWithChildren<{ role?: Role }>>;
105
+
106
+ const Content = ({ classNames, children }: ContentProps) => {
107
+ return (
108
+ <Gameboard.Content grow contain classNames={classNames}>
109
+ {children}
110
+ </Gameboard.Content>
111
+ );
112
+ };
113
+
114
+ //
115
+ // Board
116
+ //
117
+
118
+ const BOARD_NAME = 'Chessboard.Board';
119
+
120
+ type BoardProps = NaturalChessboardProps;
121
+
122
+ const Board = (props: BoardProps) => {
123
+ const registry = useContext(RegistryContext);
124
+ const { model } = useGameboardContext<ChessModel>(BOARD_NAME);
125
+
126
+ // Keyboard navigation.
127
+ const ref = useRef<HTMLDivElement>(null);
128
+ useEffect(() => {
129
+ if (!ref.current) {
130
+ return;
131
+ }
132
+
133
+ // Participate in keyboard navigation (set tabIndex={0})
134
+ ref.current.setAttribute('data-arrow-keys', 'all');
135
+
136
+ return addEventListener(ref.current, 'keydown', (ev) => {
137
+ const moveIndex = registry.get(model.moveIndex);
138
+ switch (ev.key) {
139
+ case 'ArrowUp':
140
+ model.setMoveIndex(0);
141
+ break;
142
+ case 'ArrowDown':
143
+ model.setMoveIndex(model.game.history().length);
144
+ break;
145
+ case 'ArrowLeft':
146
+ if (moveIndex > 0) {
147
+ model.setMoveIndex(moveIndex - 1);
148
+ }
149
+ break;
150
+ case 'ArrowRight':
151
+ if (moveIndex < model.game.history().length) {
152
+ model.setMoveIndex(moveIndex + 1);
153
+ }
154
+ break;
155
+ }
156
+ });
157
+ }, [registry, model]);
158
+
159
+ return <NaturalChessboard {...props} ref={ref} />;
160
+ };
161
+
162
+ Board.displayName = BOARD_NAME;
163
+
164
+ //
165
+ // Chessboard
166
+ //
167
+
168
+ export const Chessboard = {
169
+ Root: Root,
170
+ Content: Content,
171
+ Board: Board,
172
+ Info: Info,
173
+ };
174
+
175
+ export type {
176
+ RootProps as ChessboardRootProps,
177
+ ContentProps as ChessboardContentProps,
178
+ BoardProps as ChessboardBoardProps,
179
+ InfoProps as ChessboardInfoProps,
180
+ };
@@ -0,0 +1,193 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { type JSX, type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
7
+
8
+ import { Icon, IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
+ import { type Player, useGameboardContext } from '@dxos/react-ui-gameboard';
10
+ import { mx } from '@dxos/ui-theme';
11
+
12
+ import { meta } from '#meta';
13
+
14
+ import { type ExtendedChessModel } from './types';
15
+
16
+ const INFO_NAME = 'Chessboard.Info';
17
+
18
+ export type InfoProps = ThemedClassName<
19
+ {
20
+ orientation?: Player;
21
+ onOrientationChange?: (orientation: Player) => void;
22
+ onClose?: () => void;
23
+ } & Pick<HistoryProps, 'min' | 'max' | 'onSelect'>
24
+ >;
25
+
26
+ export const Info = ({ classNames, orientation = 'white', onOrientationChange, onClose, ...props }: InfoProps) => {
27
+ const { t } = useTranslation(meta.id);
28
+ const { model } = useGameboardContext<ExtendedChessModel>(INFO_NAME);
29
+
30
+ return (
31
+ <div
32
+ className={mx(
33
+ 'grid grid-rows-[min-content_1fr_min-content] w-full min-w-[18rem] p-2 overflow-hidden bg-group-surface rounded-xs',
34
+ classNames,
35
+ )}
36
+ >
37
+ <PlayerIndicator
38
+ model={model}
39
+ player={orientation === 'white' ? 'black' : 'white'}
40
+ icon={
41
+ 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
+ />
50
+ )
51
+ }
52
+ />
53
+
54
+ <History model={model} {...props} />
55
+
56
+ <PlayerIndicator
57
+ model={model}
58
+ player={orientation}
59
+ icon={
60
+ onOrientationChange && (
61
+ <IconButton
62
+ classNames={mx('transition duration-200 ease-linear', orientation === 'white' && 'rotate-180')}
63
+ icon='ph--arrows-clockwise--regular'
64
+ iconOnly
65
+ label={t('flip-board.button')}
66
+ size={4}
67
+ onClick={() => onOrientationChange(orientation === 'white' ? 'black' : 'white')}
68
+ />
69
+ )
70
+ }
71
+ />
72
+ </div>
73
+ );
74
+ };
75
+
76
+ Info.displayName = INFO_NAME;
77
+
78
+ //
79
+ // History
80
+ //
81
+
82
+ type HistoryProps = ThemedClassName<{
83
+ model: ExtendedChessModel;
84
+ min?: number;
85
+ max?: number;
86
+ onSelect?: (index: number) => void;
87
+ }>;
88
+
89
+ const History = ({ classNames, model, min, max, onSelect }: HistoryProps) => {
90
+ const { t } = useTranslation(meta.id);
91
+ const moveIndex = useAtomValue(model.moveIndex);
92
+ const label = model.game.isGameOver()
93
+ ? model.game.isCheckmate()
94
+ ? t('game.checkmate.label')
95
+ : model.game.isStalemate()
96
+ ? t('game.stalemate.label')
97
+ : t('game.draw.label')
98
+ : model.game.isCheck()
99
+ ? t('game.check.label')
100
+ : undefined;
101
+
102
+ const history = model.game.history();
103
+ const moves = useMemo(
104
+ () =>
105
+ history.reduce(
106
+ (acc, move, index) => {
107
+ if (index % 2 === 0) {
108
+ acc.push([{ index, move }]);
109
+ } else {
110
+ acc[acc.length - 1].push({ index, move });
111
+ }
112
+ return acc;
113
+ },
114
+ [] as { index: number; move: string }[][],
115
+ ),
116
+ [history.length],
117
+ );
118
+
119
+ const scrollerRef = useRef<HTMLDivElement>(null);
120
+ useEffect(() => {
121
+ scrollerRef.current?.scrollTo({ top: scrollerRef.current.scrollHeight });
122
+ }, [history.length]);
123
+
124
+ useEffect(() => {
125
+ const div = scrollerRef.current?.querySelector(`[data-index="${moveIndex - 1}"]`);
126
+ scrollerRef.current?.classList.add('scrollbar-none');
127
+ div?.scrollIntoView({ behavior: 'smooth' });
128
+ scrollerRef.current?.classList.remove('scrollbar-none');
129
+ }, [moveIndex]);
130
+
131
+ return (
132
+ <div
133
+ ref={scrollerRef}
134
+ className={mx('overflow-y-auto', classNames)}
135
+ style={{
136
+ minHeight: min === undefined ? 'auto' : `${min * 24}px`,
137
+ maxHeight: max === undefined ? 'auto' : `${max * 24}px`,
138
+ }}
139
+ >
140
+ {moves.map(([a, b], index) => (
141
+ <div key={index} className='grid grid-cols-[3rem_1fr_1fr_1rem] gap-2 ps-4'>
142
+ <div className='content-center text-xs text-subdued'>{index + 1}</div>
143
+ {a && (
144
+ <div
145
+ data-index={a.index}
146
+ className={mx('ps-2 text-sm cursor-pointer', a.index === moveIndex - 1 && 'bg-primary-500')}
147
+ onClick={() => onSelect?.(a.index + 1)}
148
+ >
149
+ {a.move}
150
+ </div>
151
+ )}
152
+ {b && (
153
+ <div
154
+ data-index={b.index}
155
+ className={mx('ps-2 text-sm cursor-pointer', b.index === moveIndex - 1 && 'bg-primary-500')}
156
+ onClick={() => onSelect?.(b.index + 1)}
157
+ >
158
+ {b.move}
159
+ </div>
160
+ )}
161
+ </div>
162
+ ))}
163
+ {label && <div className='text-center'>{label}</div>}
164
+ </div>
165
+ );
166
+ };
167
+
168
+ //
169
+ // PlayerIndicator
170
+ //
171
+
172
+ type PlayerIndicatorProps = PropsWithChildren<{
173
+ model: ExtendedChessModel;
174
+ player: Player;
175
+ icon?: JSX.Element;
176
+ }>;
177
+
178
+ const PlayerIndicator = ({ children, model, player, icon }: PlayerIndicatorProps) => {
179
+ const turn = player === (model.game.turn() === 'w' ? 'white' : 'black');
180
+ return (
181
+ <div className='grid grid-cols-[2rem_1fr_2rem] gap-2 h-(--dx-rail-size) px-1 flex items-center overflow-hidden'>
182
+ <div className='place-items-center'>
183
+ <Icon
184
+ icon={turn ? 'ph--circle--fill' : 'ph--circle--thin'}
185
+ size={6}
186
+ classNames={mx(turn && (model.game.isCheckmate() ? 'text-error-500' : 'text-success-text'))}
187
+ />
188
+ </div>
189
+ <div className='truncate overflow-hidden items-center'>{children}</div>
190
+ {icon}
191
+ </div>
192
+ );
193
+ };
@@ -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/types';
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/types';
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';
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type ComponentType, lazy } from 'react';
6
+
7
+ export const ChessArticle: ComponentType<any> = lazy(() => import('./ChessArticle'));
8
+ export const ChessCard: ComponentType<any> = lazy(() => import('./ChessCard'));
package/src/index.ts CHANGED
@@ -2,10 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './capabilities';
6
- export * from './components';
7
- export * from './functions';
5
+ export * from './blueprints';
8
6
  export * from './meta';
9
7
  export * from './types';
10
-
11
- export * from './ChessPlugin';