@mirasen/chessboard 1.0.4 → 1.0.6
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 +15 -0
- package/README.md +8 -0
- package/dist/extensions/build/index.d.ts +1 -0
- package/dist/extensions/build/index.js +1 -0
- package/dist/extensions/first-party/promotion/normalize.js +6 -4
- package/dist/extensions/types/wrapper.d.ts +0 -3
- package/dist/extensions/types/wrapper.js +0 -3
- package/dist/render/scheduler/scheduler.js +7 -10
- package/dist/runtime/factory/input.js +6 -7
- package/dist/runtime/input/adapter/factory.js +1 -1
- package/dist/runtime/mutation/interaction.js +1 -1
- package/dist/runtime/mutation/types.d.ts +3 -2
- package/dist/wrapper/factory.d.ts +1 -1
- package/dist/wrapper/factory.js +8 -0
- package/dist/wrapper/types.d.ts +15 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @mirasen/chessboard
|
|
2
2
|
|
|
3
|
+
## 1.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e81b3fd: Fix scheduler `flushNow()` so pending frames flush synchronously.
|
|
8
|
+
|
|
9
|
+
## 1.0.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0422020: Fix fast drag-and-drop sometimes leaving a stale drag visual due to an unsynced target square.
|
|
14
|
+
- 0422020: Update dependencies
|
|
15
|
+
- 0422020: Fix promotion extension to accept renderer-compatible piece URL maps without throwing on extra entries.
|
|
16
|
+
- 0422020: Allow createBoard options to configure built-in extensions without direct factory calls.
|
|
17
|
+
|
|
3
18
|
## 1.0.4
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/@mirasen/chessboard)
|
|
2
2
|
[](https://github.com/mirasen-io/chessboard/actions/workflows/ci.yml)
|
|
3
|
+
[](https://sonarcloud.io/summary/new_code?id=mirasen-io_chessboard)
|
|
4
|
+
[](https://sonarcloud.io/summary/new_code?id=mirasen-io_chessboard)
|
|
3
5
|
[](./LICENSE)
|
|
4
6
|
|
|
5
7
|
# @mirasen/chessboard
|
|
@@ -37,9 +39,15 @@ The default first-party extension baseline includes rendering, events, selection
|
|
|
37
39
|
|
|
38
40
|
That means the board already covers a meaningful baseline chess UX instead of stopping at “draw squares and pieces”.
|
|
39
41
|
|
|
42
|
+
### Built-in chessboard behavior
|
|
43
|
+
|
|
44
|
+
Mirasen Chessboard provides a complete modern interaction and visual baseline out of the box: selection, deselection, reselection, drag-to-move, release targeting, legal destinations, promotion, auto-promotion, and state-diff animation. Moves are resolved on release rather than committed immediately on press, giving users a more forgiving interaction flow. Because animation is driven by board-state transitions, pieces can move, appear, disappear, promote, or return smoothly without app-level glue code.
|
|
45
|
+
|
|
40
46
|
## Highlights
|
|
41
47
|
|
|
42
48
|
- Real built-in chess interaction
|
|
49
|
+
- Easy game/rules layer integration
|
|
50
|
+
- State-diff animation beyond simple move events
|
|
43
51
|
- Extension-driven architecture
|
|
44
52
|
- First-party baseline extensions
|
|
45
53
|
- Explicit SVG slot ownership
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { cleanAnimationPlan, prepareAnimationPlan, renderAnimationPlan } from '../../animation/render/plan.js';
|
|
1
2
|
export { clearElementChildren, createSvgElement, updateElementAttributes } from '../../render/svg/helpers.js';
|
|
2
3
|
export type { RuntimeReadonlyMutationSession } from '../../runtime/mutation/types.js';
|
|
3
4
|
export { isNonEmptyPieceCode } from '../../state/board/check.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { cleanAnimationPlan, prepareAnimationPlan, renderAnimationPlan } from '../../animation/render/plan.js';
|
|
1
2
|
export { clearElementChildren, createSvgElement, updateElementAttributes } from '../../render/svg/helpers.js';
|
|
2
3
|
export { isNonEmptyPieceCode } from '../../state/board/check.js';
|
|
3
4
|
export { fileOf, rankOf, squareOf } from '../../state/board/coords.js';
|
|
@@ -6,21 +6,23 @@ function assertPieceUrlsComplete(pieceUrls) {
|
|
|
6
6
|
assert(pieceCode in pieceUrls, `Missing URL for piece code: ${pieceCode}`);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
9
|
+
function isPromotionPieceCode(pieceCode) {
|
|
10
|
+
return (pieceCode === 5 /* PieceCode.WhiteQueen */ ||
|
|
11
11
|
pieceCode === 4 /* PieceCode.WhiteRook */ ||
|
|
12
12
|
pieceCode === 3 /* PieceCode.WhiteBishop */ ||
|
|
13
13
|
pieceCode === 2 /* PieceCode.WhiteKnight */ ||
|
|
14
14
|
pieceCode === 13 /* PieceCode.BlackQueen */ ||
|
|
15
15
|
pieceCode === 12 /* PieceCode.BlackRook */ ||
|
|
16
16
|
pieceCode === 11 /* PieceCode.BlackBishop */ ||
|
|
17
|
-
pieceCode === 10 /* PieceCode.BlackKnight
|
|
17
|
+
pieceCode === 10 /* PieceCode.BlackKnight */);
|
|
18
18
|
}
|
|
19
19
|
function normalizePromotionPieceUrls(input) {
|
|
20
20
|
const normalized = {};
|
|
21
21
|
for (const [key, url] of Object.entries(input)) {
|
|
22
22
|
const pieceCode = normalizePiece(key);
|
|
23
|
-
|
|
23
|
+
if (!isPromotionPieceCode(pieceCode)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
24
26
|
normalized[pieceCode] = url;
|
|
25
27
|
}
|
|
26
28
|
assertPieceUrlsComplete(normalized);
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public built-in extension ids accepted by createBoard({ extensions })
|
|
3
|
-
*/
|
|
4
1
|
import { createActiveTarget } from '../first-party/active-target/factory.js';
|
|
5
2
|
import { createAutoPromote } from '../first-party/auto-promote/factory.js';
|
|
6
3
|
import { createBoardEvents } from '../first-party/board-events/factory.js';
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public built-in extension ids accepted by createBoard({ extensions })
|
|
3
|
-
*/
|
|
4
1
|
import { createActiveTarget } from '../first-party/active-target/factory.js';
|
|
5
2
|
import { EXTENSION_ID as EXTENSION_ID_ACTIVE_TARGET } from '../first-party/active-target/types.js';
|
|
6
3
|
import { createAutoPromote } from '../first-party/auto-promote/factory.js';
|
|
@@ -3,16 +3,14 @@ export function createScheduler(opts) {
|
|
|
3
3
|
let scheduled = false;
|
|
4
4
|
let runId = 0;
|
|
5
5
|
let rafHandle = null;
|
|
6
|
-
const flushCore = () => {
|
|
7
|
-
if (!scheduled && rafHandle == null) {
|
|
6
|
+
const flushCore = (force = false) => {
|
|
7
|
+
if (!force && !scheduled && rafHandle == null) {
|
|
8
8
|
// Maybe was cancelled after the rAF callback was scheduled but before it fired.
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
// Reset scheduled flags first to allow schedule() during render to queue the next frame.
|
|
12
12
|
scheduled = false;
|
|
13
|
-
|
|
14
|
-
rafHandle = null;
|
|
15
|
-
}
|
|
13
|
+
rafHandle = null;
|
|
16
14
|
render();
|
|
17
15
|
};
|
|
18
16
|
return {
|
|
@@ -29,17 +27,16 @@ export function createScheduler(opts) {
|
|
|
29
27
|
});
|
|
30
28
|
},
|
|
31
29
|
flushNow() {
|
|
30
|
+
if (!scheduled && rafHandle == null)
|
|
31
|
+
return;
|
|
32
32
|
// Invalidate any pending rAF render immediately.
|
|
33
|
-
|
|
34
|
-
runId++;
|
|
35
|
-
scheduled = false;
|
|
36
|
-
}
|
|
33
|
+
runId++;
|
|
37
34
|
if (rafHandle != null) {
|
|
38
35
|
const caf = globalThis.cancelAnimationFrame;
|
|
39
36
|
caf(rafHandle);
|
|
40
37
|
rafHandle = null;
|
|
41
38
|
}
|
|
42
|
-
flushCore();
|
|
39
|
+
flushCore(true);
|
|
43
40
|
},
|
|
44
41
|
cancel() {
|
|
45
42
|
scheduled = false;
|
|
@@ -42,23 +42,24 @@ export function createRuntimeInteractionSurface(state) {
|
|
|
42
42
|
const internalState = state();
|
|
43
43
|
const mutationSession = internalState.mutation.getSession();
|
|
44
44
|
const interaction = internalState.state.interaction;
|
|
45
|
+
interaction.updateDragSessionCurrentTarget(target, mutationSession);
|
|
45
46
|
const dragSession = interaction.dragSession;
|
|
46
|
-
assert(dragSession !== null, 'completeDragTo requires an active drag session');
|
|
47
47
|
assert(isDragSessionCoreOwned(dragSession), 'completeDragTo can only be called for core-owned drag sessions');
|
|
48
48
|
uiMoveCompleteTo(internalState, target);
|
|
49
|
-
mutationSession.addMutation('runtime.interaction.
|
|
49
|
+
mutationSession.addMutation('runtime.interaction.completeCoreDragTo', true, dragSession);
|
|
50
50
|
runtimeRunMutationPipeline(internalState);
|
|
51
51
|
},
|
|
52
52
|
completeExtensionDrag(target) {
|
|
53
53
|
const internalState = state();
|
|
54
54
|
const mutationSession = internalState.mutation.getSession();
|
|
55
55
|
const interaction = internalState.state.interaction;
|
|
56
|
+
interaction.updateDragSessionCurrentTarget(target, mutationSession);
|
|
56
57
|
const dragSession = interaction.dragSession;
|
|
57
|
-
assert(dragSession !== null, 'completeExtensionDrag requires an active drag session');
|
|
58
58
|
assert(!isDragSessionCoreOwned(dragSession), 'completeExtensionDrag can only be called for extension-owned drag sessions');
|
|
59
|
-
assert(dragSession.targetSquare === target, 'The target square in completeExtensionDrag must match the current target square of the drag session');
|
|
60
59
|
internalState.extensionSystem.completeDrag(dragSession);
|
|
61
60
|
internalState.state.interaction.setDragSession(null, mutationSession);
|
|
61
|
+
mutationSession.addMutation('runtime.interaction.completeExtensionDragTo', true, dragSession);
|
|
62
|
+
runtimeRunMutationPipeline(internalState);
|
|
62
63
|
},
|
|
63
64
|
startReleaseTargetingDrag(source, target) {
|
|
64
65
|
const internalState = state();
|
|
@@ -92,9 +93,7 @@ export function createRuntimeInteractionSurface(state) {
|
|
|
92
93
|
const internalState = state();
|
|
93
94
|
const interactionMutationSession = internalState.mutation.getSession();
|
|
94
95
|
const interaction = internalState.state.interaction;
|
|
95
|
-
|
|
96
|
-
assert(currentDragSession !== null, 'No active drag session to update');
|
|
97
|
-
internalState.state.interaction.updateDragSessionCurrentTarget(target, interactionMutationSession);
|
|
96
|
+
interaction.updateDragSessionCurrentTarget(target, interactionMutationSession);
|
|
98
97
|
runtimeRunMutationPipeline(internalState);
|
|
99
98
|
},
|
|
100
99
|
onEvent(context) {
|
|
@@ -19,7 +19,7 @@ export function createInputAdapter(options) {
|
|
|
19
19
|
internalState.container.addEventListener(type, onEventHander);
|
|
20
20
|
},
|
|
21
21
|
unsubscribeEvent(type) {
|
|
22
|
-
assert(NEED_EVENT_TYPES.has(type), `Unsubscribe for event type ${type} is not supported. This is required event type for the board to function properly.`);
|
|
22
|
+
assert(!NEED_EVENT_TYPES.has(type), `Unsubscribe for event type ${type} is not supported. This is required event type for the board to function properly.`);
|
|
23
23
|
internalState.container.removeEventListener(type, onEventHander);
|
|
24
24
|
},
|
|
25
25
|
destroy() {
|
|
@@ -4,7 +4,7 @@ export const reconcileInteractionSelectionAfterBoardStateChange = (context, muta
|
|
|
4
4
|
const { current } = context;
|
|
5
5
|
if (!mutationSession.hasMutation({ prefixes: ['state.board.'] }) ||
|
|
6
6
|
mutationSession.hasMutation({
|
|
7
|
-
causes: ['state.interaction.clear', 'runtime.interaction.
|
|
7
|
+
causes: ['state.interaction.clear', 'runtime.interaction.completeCoreDragTo']
|
|
8
8
|
})) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
@@ -3,10 +3,11 @@ import type { MutationSession, ReadonlyMutationSession } from '../../mutation/ty
|
|
|
3
3
|
import type { BoardStateMutationPayloadByCause } from '../../state/board/mutation.js';
|
|
4
4
|
import type { ChangeStateMutationPayloadByCause } from '../../state/change/mutation.js';
|
|
5
5
|
import type { InteractionStateMutationPayloadByCause } from '../../state/interaction/mutation.js';
|
|
6
|
-
import type { DragSessionCoreOwnedSnapshot } from '../../state/interaction/types/internal.js';
|
|
6
|
+
import type { DragSessionCoreOwnedSnapshot, DragSessionExtensionOwnedSnapshot } from '../../state/interaction/types/internal.js';
|
|
7
7
|
import type { ViewStateMutationPayloadByCause } from '../../state/view/mutation.js';
|
|
8
8
|
type RuntimeOwnMutationPayloadByCause = {
|
|
9
|
-
'runtime.interaction.
|
|
9
|
+
'runtime.interaction.completeCoreDragTo': DragSessionCoreOwnedSnapshot;
|
|
10
|
+
'runtime.interaction.completeExtensionDragTo': DragSessionExtensionOwnedSnapshot;
|
|
10
11
|
'runtime.interaction.resolveDeferredUIMoveRequest': undefined;
|
|
11
12
|
'runtime.interaction.cancelDeferredUIMoveRequest': undefined;
|
|
12
13
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { DefaultBuiltinChessboardExtensions } from '../extensions/types/wrapper.js';
|
|
2
|
-
import { Chessboard, ChessboardExtensionInput, ChessboardInitOptions } from './types.js';
|
|
2
|
+
import { type Chessboard, type ChessboardExtensionInput, type ChessboardInitOptions } from './types.js';
|
|
3
3
|
export declare function createBoard<TExtensions extends readonly ChessboardExtensionInput[] = DefaultBuiltinChessboardExtensions>(options: ChessboardInitOptions<TExtensions>): Chessboard<TExtensions>;
|
package/dist/wrapper/factory.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { builtInExtensionFactoryMap, DefaultBuiltinChessboardExtensions } from '../extensions/types/wrapper.js';
|
|
2
2
|
import { createRuntime } from '../runtime/factory/main.js';
|
|
3
|
+
function resolveBuiltInExtensionWithOptions(input) {
|
|
4
|
+
const factory = builtInExtensionFactoryMap[input.builtin];
|
|
5
|
+
return factory(input.options);
|
|
6
|
+
}
|
|
3
7
|
export function createBoard(options) {
|
|
4
8
|
// prepare init options for runtime
|
|
5
9
|
const extensionsInput = options.extensions ?? DefaultBuiltinChessboardExtensions;
|
|
@@ -8,6 +12,10 @@ export function createBoard(options) {
|
|
|
8
12
|
// built-in extension, convert to definition
|
|
9
13
|
return builtInExtensionFactoryMap[ext]();
|
|
10
14
|
}
|
|
15
|
+
if ('builtin' in ext) {
|
|
16
|
+
// built-in extension with options, convert to definition
|
|
17
|
+
return resolveBuiltInExtensionWithOptions(ext);
|
|
18
|
+
}
|
|
11
19
|
// assume it's already a definition
|
|
12
20
|
return ext;
|
|
13
21
|
});
|
package/dist/wrapper/types.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { AnyExtensionDefinition, ExtensionDefinitionId, ExtensionDefinitionPublicApi } from '../extensions/types/extension.js';
|
|
2
|
-
import { BuiltInExtensionDefinitionMap, BuiltInExtensionId, DefaultBuiltinChessboardExtensions } from '../extensions/types/wrapper.js';
|
|
3
|
-
import { Runtime } from '../runtime/types/main.js';
|
|
4
|
-
import { RuntimeStateInitOptions } from '../state/types.js';
|
|
2
|
+
import { type BuiltInExtensionDefinitionMap, type BuiltInExtensionId, type DefaultBuiltinChessboardExtensions, builtInExtensionFactoryMap } from '../extensions/types/wrapper.js';
|
|
3
|
+
import type { Runtime } from '../runtime/types/main.js';
|
|
4
|
+
import type { RuntimeStateInitOptions } from '../state/types.js';
|
|
5
5
|
/**
|
|
6
6
|
* What createBoard accepts in options.extensions
|
|
7
7
|
*/
|
|
8
|
-
export type
|
|
8
|
+
export type BuiltInExtensionFactoryInitOptions<TId extends BuiltInExtensionId> = Parameters<(typeof builtInExtensionFactoryMap)[TId]> extends [] ? never : NonNullable<Parameters<(typeof builtInExtensionFactoryMap)[TId]>[0]>;
|
|
9
|
+
type BuiltInExtensionIdsWithOptions = {
|
|
10
|
+
[TId in BuiltInExtensionId]: [BuiltInExtensionFactoryInitOptions<TId>] extends [never] ? never : TId;
|
|
11
|
+
}[BuiltInExtensionId];
|
|
12
|
+
export type BuiltInExtensionInitOptions = {
|
|
13
|
+
[TId in BuiltInExtensionIdsWithOptions]: {
|
|
14
|
+
builtin: TId;
|
|
15
|
+
options: BuiltInExtensionFactoryInitOptions<TId>;
|
|
16
|
+
};
|
|
17
|
+
}[BuiltInExtensionIdsWithOptions];
|
|
18
|
+
export type ChessboardExtensionInput = BuiltInExtensionId | BuiltInExtensionInitOptions | AnyExtensionDefinition;
|
|
9
19
|
/**
|
|
10
20
|
* Resolve one public input item into a concrete extension definition type
|
|
11
21
|
*/
|
|
12
|
-
export type ResolveChessboardExtensionInput<T> = T extends keyof BuiltInExtensionDefinitionMap ? BuiltInExtensionDefinitionMap[T] : T extends AnyExtensionDefinition ? T : never;
|
|
22
|
+
export type ResolveChessboardExtensionInput<T> = T extends keyof BuiltInExtensionDefinitionMap ? BuiltInExtensionDefinitionMap[T] : T extends BuiltInExtensionInitOptions ? T['builtin'] extends keyof BuiltInExtensionDefinitionMap ? BuiltInExtensionDefinitionMap[T['builtin']] : never : T extends AnyExtensionDefinition ? T : never;
|
|
13
23
|
/**
|
|
14
24
|
* Resolve an input tuple into a tuple of concrete extension definitions
|
|
15
25
|
*/
|
package/package.json
CHANGED