@mirasen/chessboard 1.1.0 → 1.1.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @mirasen/chessboard
2
2
 
3
+ ## 1.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c108b6: Export public input types for wrapper packages.
8
+
9
+ ## 1.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 8b3781c: dependabot: directory '/', update @ktarmyshov/assert
14
+ - 8b3781c: dependabot: directory '/', update es-toolkit
15
+ - 8b3781c: dependabot: directory '/', update typescript-eslint
16
+ - c877d35: dependabot: directory '/', update jsdom
17
+
3
18
  ## 1.1.0
4
19
 
5
20
  ### Minor Changes
package/README.md CHANGED
@@ -8,6 +8,12 @@
8
8
 
9
9
  A framework-agnostic TypeScript chessboard platform with real chess interaction built in and an extension-driven architecture.
10
10
 
11
+ ## Try it live
12
+
13
+ - [Play a chess.js game](https://mirasen.io/chessboard/examples/chessjs.html)
14
+ - [Try the promotion flow](https://mirasen.io/chessboard/examples/promotion.html)
15
+ - [Try the minimal example](https://mirasen.io/chessboard/examples/minimal.html)
16
+
11
17
  ## Why this exists
12
18
 
13
19
  Most chessboards fall into one of two buckets:
@@ -68,15 +74,6 @@ Rules and legality still belong to your game layer. Use the built-in `chess.js`
68
74
  npm install @mirasen/chessboard
69
75
  ```
70
76
 
71
- ## Live examples
72
-
73
- Interactive examples are available on the Mirasen website:
74
-
75
- - [Examples overview](https://mirasen.io/chessboard/examples/)
76
- - [Minimal interactive example](https://mirasen.io/chessboard/examples/minimal.html)
77
- - [Promotion example](https://mirasen.io/chessboard/examples/promotion.html)
78
- - [chess.js game example](https://mirasen.io/chessboard/examples/chessjs.html)
79
-
80
77
  ## Usage
81
78
 
82
79
  ### Minimal example
@@ -297,12 +294,16 @@ A board that only renders pieces and leaves everything else to app code often fo
297
294
 
298
295
  A board that handles everything internally without clean boundaries becomes difficult to evolve, customize, or extend.
299
296
 
297
+ ### Opt out, not build up
298
+
300
299
  `@mirasen/chessboard` aims for a middle path:
301
300
 
302
301
  - strong built-in baseline interaction
303
302
  - clean internal architecture
304
303
  - extension-driven feature growth
305
304
 
305
+ Common chessboard behavior is included by default: rendering, interaction, selection, legal target feedback, promotion, and animation. You start from a working interaction baseline and remove or replace extensions only when you want less or need something different.
306
+
306
307
  ## Project direction
307
308
 
308
309
  This platform is being built as a foundation for richer chess learning and interaction workflows, but the board itself is designed to stand as a serious open-source UI platform in its own right.
@@ -323,7 +324,3 @@ For details, see:
323
324
 
324
325
  - [./assets/pieces/chessnut/ATTRIBUTION.md](./assets/pieces/chessnut/ATTRIBUTION.md)
325
326
  - [./assets/pieces/chessnut/LICENSE.txt](./assets/pieces/chessnut/LICENSE.txt)
326
-
327
- ```
328
-
329
- ```
@@ -45,8 +45,11 @@ function createExtensionRuntimeSurfaceEvents(getInternalState, runtimeSurfaceEve
45
45
  subscribers = new Set();
46
46
  internalState.eventSubscribers.set(type, subscribers);
47
47
  }
48
+ const sizeBeforeAdd = subscribers.size;
48
49
  subscribers.add(extensionDef.id);
49
- runtimeSurfaceEvents.subscribeEvent(type);
50
+ if (sizeBeforeAdd === 0) {
51
+ runtimeSurfaceEvents.subscribeEvent(type);
52
+ }
50
53
  },
51
54
  unsubscribeEvent(type) {
52
55
  const internalState = getInternalState();
@@ -6,14 +6,14 @@ export const DEFAULT_CONFIG = {
6
6
  color: 'rgb(0, 0, 0)',
7
7
  opacity: 0.14
8
8
  },
9
- radiusRatio: 0.125
10
- }, // semi-transparent black
9
+ radiusRatio: 0.165
10
+ },
11
11
  captureTarget: {
12
12
  color: {
13
13
  color: 'rgb(0, 0, 0)',
14
14
  opacity: 0.14
15
15
  },
16
- radiusRatio: 0.45,
17
- strokeWidthRatio: 0.1
16
+ radiusRatio: 0.445,
17
+ strokeWidthRatio: 0.11
18
18
  }
19
19
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { MoveOutput } from './extensions/build/index.js';
2
- export type { MoveRequestInput, PiecePositionRecordString } from './state/board/types/input.js';
3
- export type { MoveDestinationInput } from './state/interaction/types/input.js';
2
+ export type { ColorInput, MoveRequestInput, PiecePositionRecordString, PositionInput } from './state/board/types/input.js';
3
+ export type { MovabilityInput, MoveDestinationInput } from './state/interaction/types/input.js';
4
4
  export { createBoard } from './wrapper/factory.js';
5
5
  export type { Chessboard, ChessboardExtensionInput, ChessboardInitOptions } from './wrapper/types.js';
@@ -11,7 +11,7 @@ export const updateLastMoveOnBoardMove = (context, mutationSession) => {
11
11
  export const clearLastMoveOnBoardSetPosition = (context, mutationSession) => {
12
12
  const { current } = context;
13
13
  if (!mutationSession.hasMutation({
14
- causes: ['state.board.setPosition', 'state.board.setPiecePosition']
14
+ causes: ['state.board.setPosition', 'state.board.setPiecePosition', 'state.board.setTurn']
15
15
  })) {
16
16
  return;
17
17
  }
@@ -1,12 +1,13 @@
1
1
  import { Color, ColorShort, MoveRequestInput, Piece, PiecePositionInput, PiecePositionRecord, PiecePositionRecordShort, PiecePositionRecordString, PieceShort, PieceString, Role, RoleShort, SquareString } from './types/input.js';
2
- import { EmptyPieceCode, MoveRequest, NonEmptyPieceCode, PieceCode, RolePromotionCode, Square, SquareFile, SquareRank } from './types/internal.js';
3
- import { BoardStateSnapshot } from './types/main.js';
2
+ import { EmptyPieceCode, MoveRequest, NonEmptyPieceCode, PieceCode, PiecePositionSnapshot, PositionSnapshot, RolePromotionCode, Square, SquareFile, SquareRank } from './types/internal.js';
3
+ import type { BoardStateSnapshot } from './types/main.js';
4
4
  export declare function isWhitePieceCode(code: NonEmptyPieceCode): boolean;
5
5
  export declare function isBlackPieceCode(code: NonEmptyPieceCode): boolean;
6
6
  export declare function isOccupied(board: BoardStateSnapshot, sq: Square): boolean;
7
7
  export declare function isEmptyPieceCode(code: PieceCode): code is EmptyPieceCode;
8
8
  export declare function isNonEmptyPieceCode(code: PieceCode): code is NonEmptyPieceCode;
9
- export declare function piecePositionsEqual(pos1: BoardStateSnapshot, pos2: BoardStateSnapshot): boolean;
9
+ export declare function piecePositionsEqual(pos1: PiecePositionSnapshot, pos2: PiecePositionSnapshot): boolean;
10
+ export declare function positionsEqual(pos1: PositionSnapshot, pos2: PositionSnapshot): boolean;
10
11
  export declare function isRank(n: number): n is SquareRank;
11
12
  export declare function isFile(n: number): n is SquareFile;
12
13
  export declare function isValidSquare(n: number): n is Square;
@@ -15,7 +15,7 @@ export function isNonEmptyPieceCode(code) {
15
15
  return code !== 0 /* PieceCode.Empty */;
16
16
  }
17
17
  export function piecePositionsEqual(pos1, pos2) {
18
- if (pos1.positionEpoch !== pos2.positionEpoch)
18
+ if (pos1.pieces.length !== pos2.pieces.length)
19
19
  return false;
20
20
  for (let i = 0; i < pos1.pieces.length; i++) {
21
21
  if (pos1.pieces[i] !== pos2.pieces[i])
@@ -23,6 +23,11 @@ export function piecePositionsEqual(pos1, pos2) {
23
23
  }
24
24
  return true;
25
25
  }
26
+ export function positionsEqual(pos1, pos2) {
27
+ if (pos1.turn !== pos2.turn)
28
+ return false;
29
+ return piecePositionsEqual(pos1, pos2);
30
+ }
26
31
  export function isRank(n) {
27
32
  return Number.isInteger(n) && n >= 0 && n < 8;
28
33
  }
@@ -2,7 +2,7 @@ import { cloneDeep } from 'es-toolkit/object';
2
2
  import { isNormalizedMoveRequest } from './check.js';
3
3
  import { normalizeColor, normalizeMoveRequest } from './normalize.js';
4
4
  import { boardParsePiecePositionInput, boardParsePosition } from './position.js';
5
- import { boardMove, boardSetPosition, boardSetTurn } from './reducers.js';
5
+ import { boardMove, boardSetPiecePosition, boardSetPosition, boardSetTurn } from './reducers.js';
6
6
  function createBoardStateInternal(position) {
7
7
  const parsedPosition = boardParsePosition(position);
8
8
  return {
@@ -16,13 +16,11 @@ export function createBoardState(position) {
16
16
  return {
17
17
  setPosition(input, mutationSession) {
18
18
  const position = boardParsePosition(input);
19
- let changed = boardSetPosition(internalState, position.pieces);
20
- changed = boardSetTurn(internalState, position.turn) || changed;
21
- return mutationSession.addMutation('state.board.setPosition', changed);
19
+ return mutationSession.addMutation('state.board.setPosition', boardSetPosition(internalState, position));
22
20
  },
23
21
  setPiecePosition(input, mutationSession) {
24
22
  const pieces = boardParsePiecePositionInput(input);
25
- return mutationSession.addMutation('state.board.setPiecePosition', boardSetPosition(internalState, pieces));
23
+ return mutationSession.addMutation('state.board.setPiecePosition', boardSetPiecePosition(internalState, pieces));
26
24
  },
27
25
  setTurn(turn, mutationSession) {
28
26
  const normalizedTurn = normalizeColor(turn);
@@ -1,10 +1,10 @@
1
1
  import { FenString } from '../types/input.js';
2
- import { ParsedPosition } from '../types/internal.js';
2
+ import { PositionSnapshot } from '../types/internal.js';
3
3
  /**
4
- * Parse a FEN string into a {@link ParsedPosition}.
4
+ * Parse a FEN string into a {@link PositionSnapshot}.
5
5
  *
6
6
  * Only the first two FEN fields (piece placement + active color) are required
7
7
  * for a board-only representation. The remaining fields (castling, en-passant,
8
8
  * half-move clock, full-move number) are accepted but ignored.
9
9
  */
10
- export declare function parseFen(fen: FenString): ParsedPosition;
10
+ export declare function parseFen(fen: FenString): PositionSnapshot;
@@ -74,7 +74,7 @@ function parseTurn(turn) {
74
74
  }
75
75
  }
76
76
  /**
77
- * Parse a FEN string into a {@link ParsedPosition}.
77
+ * Parse a FEN string into a {@link PositionSnapshot}.
78
78
  *
79
79
  * Only the first two FEN fields (piece placement + active color) are required
80
80
  * for a board-only representation. The remaining fields (castling, en-passant,
@@ -1,4 +1,4 @@
1
1
  import { PiecePositionInput, PositionInput } from './types/input.js';
2
- import { ParsedPosition } from './types/internal.js';
2
+ import { PositionSnapshot } from './types/internal.js';
3
3
  export declare function boardParsePiecePositionInput(input: PiecePositionInput): Uint8Array;
4
- export declare function boardParsePosition(input: PositionInput): ParsedPosition;
4
+ export declare function boardParsePosition(input: PositionInput): PositionSnapshot;
@@ -1,8 +1,6 @@
1
- import { ColorCode, Move, MoveRequest } from './types/internal.js';
1
+ import { ColorCode, Move, MoveRequest, PositionSnapshot } from './types/internal.js';
2
2
  import { BoardStateInternal } from './types/main.js';
3
- export declare function boardSetPosition(state: BoardStateInternal, pieces: Uint8Array): boolean;
4
- /**
5
- * Set active color turn.
6
- */
3
+ export declare function boardSetPiecePosition(state: BoardStateInternal, pieces: Uint8Array): boolean;
7
4
  export declare function boardSetTurn(state: BoardStateInternal, turn: ColorCode): boolean;
5
+ export declare function boardSetPosition(state: BoardStateInternal, position: PositionSnapshot): boolean;
8
6
  export declare function boardMove(state: BoardStateInternal, request: MoveRequest): Move;
@@ -1,19 +1,26 @@
1
- import { isEmptyPieceCode } from './check.js';
1
+ import { isEmptyPieceCode, piecePositionsEqual, positionsEqual } from './check.js';
2
2
  import { denormalizeSquare } from './denormalize.js';
3
3
  import { fromPieceCode, toPieceCode } from './piece.js';
4
- export function boardSetPosition(state, pieces) {
4
+ export function boardSetPiecePosition(state, pieces) {
5
+ if (piecePositionsEqual(state, { pieces }))
6
+ return false; // no-op
5
7
  state.pieces = new Uint8Array(pieces);
6
- // Increment position epoch to prevent false animation across position resets
7
8
  state.positionEpoch++;
8
9
  return true;
9
10
  }
10
- /**
11
- * Set active color turn.
12
- */
13
11
  export function boardSetTurn(state, turn) {
14
12
  if (state.turn === turn)
15
13
  return false; // no-op
16
14
  state.turn = turn;
15
+ state.positionEpoch++;
16
+ return true;
17
+ }
18
+ export function boardSetPosition(state, position) {
19
+ if (positionsEqual(state, position))
20
+ return false; // no-op
21
+ state.pieces = new Uint8Array(position.pieces);
22
+ state.turn = position.turn;
23
+ state.positionEpoch++;
17
24
  return true;
18
25
  }
19
26
  function buildMoveBase(state, request) {
@@ -48,7 +48,9 @@ export type MoveBaseSnapshot = ReadonlyDeep<MoveBase>;
48
48
  export type MoveSnapshot = ReadonlyDeep<Move>;
49
49
  export declare const FILE_START: number;
50
50
  export declare const RANK_START: number;
51
- export type ParsedPosition = {
52
- pieces: Uint8Array;
53
- turn: ColorCode;
54
- };
51
+ export interface PiecePositionSnapshot {
52
+ readonly pieces: Readonly<Uint8Array>;
53
+ }
54
+ export interface PositionSnapshot extends PiecePositionSnapshot {
55
+ readonly turn: ColorCode;
56
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mirasen/chessboard",
3
3
  "description": "Framework-agnostic TypeScript chessboard platform with real chess interaction built in.",
4
- "version": "1.1.0",
4
+ "version": "1.1.2",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Mirasen",
@@ -61,7 +61,7 @@
61
61
  "check": "tsc --skipLibCheck --noEmit",
62
62
  "check:test": "tsc --project tsconfig-test.json --skipLibCheck --noEmit",
63
63
  "test": "npm run check:test && vitest run --run",
64
- "coverage": "vitest run --coverage",
64
+ "coverage": "npm run check:test && vitest run --coverage",
65
65
  "changeset:version": "changeset version && npm install && npm run format && git add --all",
66
66
  "changeset:publish": "changeset publish"
67
67
  },