@mirasen/chessboard 1.2.3 → 1.3.0
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 +46 -0
- package/README.md +9 -0
- package/dist/animation/planner.js +2 -1
- package/dist/extensions/build/index.d.ts +1 -1
- package/dist/extensions/first-party/annotations/interaction.js +4 -2
- package/dist/extensions/first-party/annotations/update.js +1 -1
- package/dist/extensions/first-party/board-events/factory.js +2 -2
- package/dist/extensions/first-party/main-renderer/animation/factory.d.ts +2 -1
- package/dist/extensions/first-party/main-renderer/animation/factory.js +3 -2
- package/dist/extensions/first-party/main-renderer/animation/types.d.ts +2 -0
- package/dist/extensions/first-party/main-renderer/animation/update.js +4 -2
- package/dist/extensions/first-party/main-renderer/board/factory.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/board/factory.js +4 -4
- package/dist/extensions/first-party/main-renderer/board/render.js +1 -1
- package/dist/extensions/first-party/main-renderer/board/types.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/coordinates/factory.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/coordinates/factory.js +2 -2
- package/dist/extensions/first-party/main-renderer/coordinates/render.js +1 -1
- package/dist/extensions/first-party/main-renderer/coordinates/types.d.ts +1 -1
- package/dist/extensions/first-party/main-renderer/denormalize.d.ts +3 -0
- package/dist/extensions/first-party/main-renderer/denormalize.js +18 -0
- package/dist/extensions/first-party/main-renderer/drag/factory.d.ts +2 -1
- package/dist/extensions/first-party/main-renderer/drag/factory.js +2 -1
- package/dist/extensions/first-party/main-renderer/drag/render.js +18 -11
- package/dist/extensions/first-party/main-renderer/drag/types.d.ts +2 -0
- package/dist/extensions/first-party/main-renderer/drag/update.js +4 -2
- package/dist/extensions/first-party/main-renderer/factory.d.ts +2 -1
- package/dist/extensions/first-party/main-renderer/factory.js +46 -12
- package/dist/extensions/first-party/main-renderer/normalize.d.ts +3 -3
- package/dist/extensions/first-party/main-renderer/normalize.js +19 -7
- package/dist/extensions/first-party/main-renderer/pieces/suppress.js +2 -1
- package/dist/extensions/first-party/main-renderer/public-defaults.d.ts +3 -0
- package/dist/extensions/first-party/main-renderer/public-defaults.js +4 -0
- package/dist/extensions/first-party/main-renderer/types/extension.d.ts +8 -5
- package/dist/extensions/first-party/main-renderer/types/instance.d.ts +10 -8
- package/dist/extensions/first-party/main-renderer/types/internal.d.ts +6 -7
- package/dist/extensions/first-party/main-renderer/types/internal.js +24 -1
- package/dist/extensions/first-party/main-renderer/types/public.d.ts +9 -0
- package/dist/extensions/first-party/main-renderer/types/template.d.ts +12 -1
- package/dist/extensions/first-party/promotion/types/input.d.ts +2 -2
- package/dist/extensions/index.d.ts +1 -0
- package/dist/extensions/index.js +1 -0
- package/dist/extensions/types/basic/events.d.ts +32 -16
- package/dist/extensions/types/basic/interaction.d.ts +13 -2
- package/dist/extensions/types/surface/commands.d.ts +4 -2
- package/dist/runtime/factory/input.js +50 -22
- package/dist/runtime/factory/main.js +11 -0
- package/dist/runtime/input/adapter/types.js +2 -1
- package/dist/runtime/input/controller/factory.js +36 -9
- package/dist/runtime/input/controller/helpers.d.ts +6 -0
- package/dist/runtime/input/controller/helpers.js +27 -0
- package/dist/runtime/input/controller/pointer.js +71 -52
- package/dist/runtime/input/controller/types.d.ts +13 -4
- package/dist/runtime/mutation/interaction.js +1 -1
- package/dist/runtime/mutation/types.d.ts +2 -2
- package/dist/state/interaction/config.d.ts +6 -0
- package/dist/state/interaction/config.js +30 -0
- package/dist/state/interaction/factory.js +15 -3
- package/dist/state/interaction/helpers.d.ts +18 -2
- package/dist/state/interaction/helpers.js +12 -0
- package/dist/state/interaction/mutation.d.ts +2 -0
- package/dist/state/interaction/reducers.d.ts +3 -0
- package/dist/state/interaction/reducers.js +22 -7
- package/dist/state/interaction/types/config.d.ts +9 -0
- package/dist/state/interaction/types/config.js +1 -0
- package/dist/state/interaction/types/input.d.ts +3 -0
- package/dist/state/interaction/types/internal.d.ts +14 -4
- package/dist/state/interaction/types/main.d.ts +11 -5
- package/dist/wrapper/factory.js +2 -0
- package/dist/wrapper/types.d.ts +1 -1
- package/package.json +5 -6
- package/dist/extensions/first-party/main-renderer/types/input.d.ts +0 -4
- /package/dist/extensions/first-party/main-renderer/types/{input.js → public.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from '@ktarmyshov/assert';
|
|
2
2
|
import { isEmptyPieceCode, isNonEmptyPieceCode } from '../../../state/board/check.js';
|
|
3
3
|
import { fromPieceCode } from '../../../state/board/piece.js';
|
|
4
|
-
import { isDragSessionCoreOwned } from '../../../state/interaction/helpers.js';
|
|
5
|
-
import { canMoveTo } from './helpers.js';
|
|
4
|
+
import { isDragSessionActiveLiftedPiece, isDragSessionCoreOwned, isDragSessionPendingLiftedPiece, isDragSessionReleaseTargeting } from '../../../state/interaction/helpers.js';
|
|
5
|
+
import { buttonToButtonsMask, canMoveTo, isMovementBeyondThreshold } from './helpers.js';
|
|
6
6
|
export function determineActionPointerDown(state, context) {
|
|
7
7
|
assert(context.rawEvent.type === 'pointerdown', 'determineActionPointerDown should only be called for pointerdown events');
|
|
8
8
|
const rawEvent = context.rawEvent;
|
|
@@ -29,9 +29,10 @@ export function determineActionPointerDown(state, context) {
|
|
|
29
29
|
const targetPieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
|
|
30
30
|
if (isEmptyPieceCode(targetPieceCode)) {
|
|
31
31
|
return {
|
|
32
|
-
type: '
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
type: 'startReleaseTargetingDragSession',
|
|
33
|
+
sourceSquare: interaction.selected.square,
|
|
34
|
+
targetSquare: sceneEvent.targetSquare,
|
|
35
|
+
startButton: rawEvent.button
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
const isLegalMoveTarget = interaction.movability.mode === 1 /* MovabilityModeCode.Free */ ||
|
|
@@ -40,9 +41,10 @@ export function determineActionPointerDown(state, context) {
|
|
|
40
41
|
if (fromPieceCode(targetPieceCode).color !== fromPieceCode(selectedPieceCode).color &&
|
|
41
42
|
isLegalMoveTarget) {
|
|
42
43
|
return {
|
|
43
|
-
type: '
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
type: 'startReleaseTargetingDragSession',
|
|
45
|
+
sourceSquare: interaction.selected.square,
|
|
46
|
+
targetSquare: sceneEvent.targetSquare,
|
|
47
|
+
startButton: rawEvent.button
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -52,10 +54,24 @@ export function determineActionPointerDown(state, context) {
|
|
|
52
54
|
*/
|
|
53
55
|
const pieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
|
|
54
56
|
if (!isEmptyPieceCode(pieceCode)) {
|
|
57
|
+
const thresholdPx = interaction.config.drag.liftedActivation.thresholdPx;
|
|
58
|
+
if (thresholdPx > 0) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'startLiftedDragSession',
|
|
61
|
+
phase: 'pending',
|
|
62
|
+
sourceSquare: sceneEvent.targetSquare,
|
|
63
|
+
targetSquare: sceneEvent.targetSquare,
|
|
64
|
+
startButton: rawEvent.button,
|
|
65
|
+
startPoint: sceneEvent.point,
|
|
66
|
+
thresholdPx
|
|
67
|
+
};
|
|
68
|
+
}
|
|
55
69
|
return {
|
|
56
|
-
type: '
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
type: 'startLiftedDragSession',
|
|
71
|
+
phase: 'active',
|
|
72
|
+
sourceSquare: sceneEvent.targetSquare,
|
|
73
|
+
targetSquare: sceneEvent.targetSquare,
|
|
74
|
+
startButton: rawEvent.button
|
|
59
75
|
};
|
|
60
76
|
}
|
|
61
77
|
}
|
|
@@ -64,67 +80,65 @@ export function determineActionPointerDown(state, context) {
|
|
|
64
80
|
export function determineActionPointerMove(state, context) {
|
|
65
81
|
assert(context.rawEvent.type === 'pointermove', 'determineActionPointerMove should only be called for pointermove events');
|
|
66
82
|
const interaction = state.surface.getInteractionStateSnapshot();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
target: context.sceneEvent?.targetSquare ?? null
|
|
71
|
-
};
|
|
83
|
+
const dragSession = interaction.dragSession;
|
|
84
|
+
if (!dragSession) {
|
|
85
|
+
return null;
|
|
72
86
|
}
|
|
73
|
-
|
|
87
|
+
const sceneEvent = context.sceneEvent;
|
|
88
|
+
assert(sceneEvent, 'pointermove requires a scene event');
|
|
89
|
+
if (isDragSessionPendingLiftedPiece(dragSession)) {
|
|
90
|
+
if (isMovementBeyondThreshold(dragSession.startPoint, sceneEvent.point, dragSession.thresholdPx)) {
|
|
91
|
+
return {
|
|
92
|
+
type: 'activatePendingLiftedDragSession',
|
|
93
|
+
targetSquare: sceneEvent.targetSquare
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { type: 'updateDragSessionCurrentTarget', targetSquare: sceneEvent.targetSquare };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
type: 'updateDragSessionCurrentTarget',
|
|
100
|
+
targetSquare: sceneEvent.targetSquare
|
|
101
|
+
};
|
|
74
102
|
}
|
|
75
|
-
|
|
76
|
-
assert(context.rawEvent.type === 'pointerup', 'determineActionPointerUp should only be called for pointerup events');
|
|
103
|
+
function determineActionTerminalRelease(state, context) {
|
|
77
104
|
const interaction = state.surface.getInteractionStateSnapshot();
|
|
78
105
|
const dragSession = interaction.dragSession;
|
|
79
106
|
if (!dragSession) {
|
|
80
|
-
// No active drag session, so nothing to do on pointer up
|
|
81
107
|
return null;
|
|
82
108
|
}
|
|
83
109
|
const sceneEvent = context.sceneEvent;
|
|
84
|
-
assert(sceneEvent, 'Scene event should be present for
|
|
110
|
+
assert(sceneEvent, 'Scene event should be present for terminal release');
|
|
85
111
|
if (!isDragSessionCoreOwned(dragSession)) {
|
|
86
|
-
// For extension-owned drag sessions, we simply end the session without attempting to make a move,
|
|
87
|
-
// since the runtime doesn't have enough information about the semantics of the drag session.
|
|
88
112
|
return {
|
|
89
|
-
type: '
|
|
90
|
-
|
|
113
|
+
type: 'completeExtensionDragSession',
|
|
114
|
+
targetSquare: sceneEvent.targetSquare
|
|
91
115
|
};
|
|
92
116
|
}
|
|
93
|
-
// Check if the square is the same as the source square of the drag session.
|
|
94
|
-
// If it is, then we can end the drag session without making a move.
|
|
95
117
|
if (sceneEvent.targetSquare === dragSession.sourceSquare) {
|
|
96
|
-
return {
|
|
97
|
-
type: 'cancelActiveInteraction'
|
|
98
|
-
};
|
|
118
|
+
return { type: 'cancelActiveInteraction' };
|
|
99
119
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
target: sceneEvent.targetSquare
|
|
105
|
-
};
|
|
120
|
+
if (sceneEvent.targetSquare !== null &&
|
|
121
|
+
canMoveTo(interaction, sceneEvent.targetSquare) &&
|
|
122
|
+
(isDragSessionActiveLiftedPiece(dragSession) || isDragSessionReleaseTargeting(dragSession))) {
|
|
123
|
+
return { type: 'completeCoreDragSessionTo', targetSquare: sceneEvent.targetSquare };
|
|
106
124
|
}
|
|
107
|
-
// Invalid target: piece returns to source for lifted drag (selection preserved),
|
|
108
|
-
// or selection is cleared for release targeting.
|
|
109
125
|
if (dragSession.type === 'lifted-piece-drag') {
|
|
110
|
-
return {
|
|
111
|
-
type: 'cancelActiveInteraction'
|
|
112
|
-
};
|
|
126
|
+
return { type: 'cancelActiveInteraction' };
|
|
113
127
|
}
|
|
114
128
|
else {
|
|
115
|
-
return {
|
|
116
|
-
type: 'cancelInteraction'
|
|
117
|
-
};
|
|
129
|
+
return { type: 'cancelInteraction' };
|
|
118
130
|
}
|
|
119
131
|
}
|
|
132
|
+
export function determineActionPointerUp(state, context) {
|
|
133
|
+
assert(context.rawEvent.type === 'pointerup', 'determineActionPointerUp should only be called for pointerup events');
|
|
134
|
+
return determineActionTerminalRelease(state, context);
|
|
135
|
+
}
|
|
120
136
|
export function determineActionPointerCancel(state, context) {
|
|
121
137
|
assert(context.rawEvent.type === 'pointercancel', 'determineActionPointerCancel should only be called for pointercancel events');
|
|
122
138
|
const interaction = state.surface.getInteractionStateSnapshot();
|
|
123
139
|
if (!interaction.dragSession) {
|
|
124
|
-
// No active drag session, so nothing to do on pointer cancel
|
|
125
140
|
return null;
|
|
126
141
|
}
|
|
127
|
-
// Cancel the active interaction on pointer cancel
|
|
128
142
|
return {
|
|
129
143
|
type: 'cancelActiveInteraction'
|
|
130
144
|
};
|
|
@@ -132,12 +146,17 @@ export function determineActionPointerCancel(state, context) {
|
|
|
132
146
|
export function determineActionLostPointerCapture(state, context) {
|
|
133
147
|
assert(context.rawEvent.type === 'lostpointercapture', 'determineActionLostPointerCapture should only be called for lostpointercapture events');
|
|
134
148
|
const interaction = state.surface.getInteractionStateSnapshot();
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
const dragSession = interaction.dragSession;
|
|
150
|
+
if (!dragSession) {
|
|
137
151
|
return null;
|
|
138
152
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
const rawEvent = context.rawEvent;
|
|
154
|
+
const mask = buttonToButtonsMask(dragSession.startButton);
|
|
155
|
+
if (mask === null) {
|
|
156
|
+
return { type: 'cancelActiveInteraction' };
|
|
157
|
+
}
|
|
158
|
+
if ((rawEvent.buttons & mask) === 0) {
|
|
159
|
+
return determineActionTerminalRelease(state, context);
|
|
160
|
+
}
|
|
161
|
+
return { type: 'cancelActiveInteraction' };
|
|
143
162
|
}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import type { TransientInput } from '../../../extensions/types/basic/transient-visuals.js';
|
|
2
2
|
import type { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
|
|
3
3
|
import type { PieceCode, Square } from '../../../state/board/types/internal.js';
|
|
4
|
+
import type { DragSessionActiveLiftedPieceCoreOwned, DragSessionPendingLiftedPieceCoreOwned, DragSessionReleaseTargetingCoreOwned } from '../../../state/interaction/types/internal.js';
|
|
4
5
|
import type { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
|
|
6
|
+
export type StartPendingLiftedDragSessionInput = Pick<DragSessionPendingLiftedPieceCoreOwned, 'phase' | 'sourceSquare' | 'targetSquare' | 'startButton' | 'startPoint' | 'thresholdPx'>;
|
|
7
|
+
export type StartActiveLiftedDragSessionInput = Pick<DragSessionActiveLiftedPieceCoreOwned, 'phase' | 'sourceSquare' | 'targetSquare' | 'startButton'>;
|
|
8
|
+
export type StartLiftedDragSessionInput = StartPendingLiftedDragSessionInput | StartActiveLiftedDragSessionInput;
|
|
9
|
+
export type ActivatePendingLiftedDragSessionInput = {
|
|
10
|
+
targetSquare: Square | null;
|
|
11
|
+
};
|
|
12
|
+
export type StartReleaseTargetingDragSessionInput = Pick<DragSessionReleaseTargetingCoreOwned, 'sourceSquare' | 'targetSquare' | 'startButton'>;
|
|
5
13
|
export interface RuntimeInteractionSurface {
|
|
6
14
|
getInteractionStateSnapshot(): InteractionStateSnapshot;
|
|
7
15
|
getPieceCodeAt(square: Square): PieceCode;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
startLiftedDragSession(input: StartLiftedDragSessionInput): void;
|
|
17
|
+
activatePendingLiftedDragSession(input: ActivatePendingLiftedDragSessionInput): void;
|
|
18
|
+
startReleaseTargetingDragSession(input: StartReleaseTargetingDragSessionInput): void;
|
|
19
|
+
completeCoreDragSessionTo(target: Square): void;
|
|
20
|
+
completeExtensionDragSession(target: Square | null): void;
|
|
12
21
|
updateDragSessionCurrentTarget(target: Square | null): void;
|
|
13
22
|
cancelActiveInteraction(): void;
|
|
14
23
|
cancelInteraction(): void;
|
|
@@ -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.completeCoreDragSessionTo']
|
|
8
8
|
})) {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
@@ -6,8 +6,8 @@ import type { InteractionStateMutationPayloadByCause } from '../../state/interac
|
|
|
6
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.
|
|
10
|
-
'runtime.interaction.
|
|
9
|
+
'runtime.interaction.completeCoreDragSessionTo': DragSessionCoreOwnedSnapshot;
|
|
10
|
+
'runtime.interaction.completeExtensionDragSession': DragSessionExtensionOwnedSnapshot;
|
|
11
11
|
'runtime.interaction.resolveDeferredUIMoveRequest': undefined;
|
|
12
12
|
'runtime.interaction.cancelDeferredUIMoveRequest': undefined;
|
|
13
13
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InteractionConfig } from './types/config.js';
|
|
2
|
+
import type { InteractionConfigInput } from './types/input.js';
|
|
3
|
+
export declare const DefaultInteractionDesktopConfig: InteractionConfig;
|
|
4
|
+
export declare const DefaultInteractionMobileConfig: InteractionConfig;
|
|
5
|
+
export declare function normalizeInteractionConfig(input: InteractionConfigInput | undefined, base: InteractionConfig): InteractionConfig;
|
|
6
|
+
export declare function areInteractionConfigsEqual(a: InteractionConfig, b: InteractionConfig): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from '@ktarmyshov/assert';
|
|
2
|
+
import { toMerged } from 'es-toolkit/object';
|
|
3
|
+
import { isEqual } from 'es-toolkit/predicate';
|
|
4
|
+
export const DefaultInteractionDesktopConfig = {
|
|
5
|
+
drag: {
|
|
6
|
+
liftedActivation: {
|
|
7
|
+
thresholdPx: 0
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export const DefaultInteractionMobileConfig = {
|
|
12
|
+
drag: {
|
|
13
|
+
liftedActivation: {
|
|
14
|
+
thresholdPx: 5
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function validateInteractionConfig(config) {
|
|
19
|
+
const { thresholdPx } = config.drag.liftedActivation;
|
|
20
|
+
assert(Number.isFinite(thresholdPx), 'drag.liftedActivation.thresholdPx must be a finite number');
|
|
21
|
+
assert(thresholdPx >= 0, 'drag.liftedActivation.thresholdPx must be >= 0');
|
|
22
|
+
}
|
|
23
|
+
export function normalizeInteractionConfig(input, base) {
|
|
24
|
+
const merged = toMerged(base, input ?? {});
|
|
25
|
+
validateInteractionConfig(merged);
|
|
26
|
+
return merged;
|
|
27
|
+
}
|
|
28
|
+
export function areInteractionConfigsEqual(a, b) {
|
|
29
|
+
return isEqual(a, b);
|
|
30
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import assert from '@ktarmyshov/assert';
|
|
2
2
|
import { cloneDeep } from 'es-toolkit';
|
|
3
|
+
import { DefaultInteractionDesktopConfig, normalizeInteractionConfig } from './config.js';
|
|
3
4
|
import { updateActiveDestinations } from './helpers.js';
|
|
4
5
|
import { normalizeMovability } from './normalize.js';
|
|
5
|
-
import { interactionClear, interactionClearActive, interactionSetDragSession, interactionSetMovability, interactionSetSelected, interactionUpdateDragSessionCurrentTarget } from './reducers.js';
|
|
6
|
+
import { interactionActivatePendingLiftedDragSession, interactionClear, interactionClearActive, interactionSetConfig, interactionSetDragSession, interactionSetMovability, interactionSetSelected, interactionUpdateDragSessionCurrentTarget } from './reducers.js';
|
|
6
7
|
const DefaultMovabilityDisabled = { mode: 0 /* MovabilityModeCode.Disabled */ };
|
|
7
8
|
/**
|
|
8
9
|
* Create a fresh interaction state with all fields set to null or false.
|
|
@@ -15,7 +16,8 @@ function createInteractionStateInternal(options) {
|
|
|
15
16
|
movability,
|
|
16
17
|
selected: null,
|
|
17
18
|
activeDestinations: new Map(),
|
|
18
|
-
dragSession: null
|
|
19
|
+
dragSession: null,
|
|
20
|
+
config: normalizeInteractionConfig(options.config, DefaultInteractionDesktopConfig)
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
export function createInteractionState(options) {
|
|
@@ -48,7 +50,7 @@ export function createInteractionState(options) {
|
|
|
48
50
|
return updateActiveDestinations(internalState, mutationSession);
|
|
49
51
|
},
|
|
50
52
|
get dragSession() {
|
|
51
|
-
return internalState.dragSession
|
|
53
|
+
return cloneDeep(internalState.dragSession);
|
|
52
54
|
},
|
|
53
55
|
setDragSession(session, mutationSession) {
|
|
54
56
|
// assert that there is no active session when setting a new session.
|
|
@@ -64,6 +66,9 @@ export function createInteractionState(options) {
|
|
|
64
66
|
}
|
|
65
67
|
return mutationSession.addMutation('state.interaction.setDragSession', interactionSetDragSession(internalState, session));
|
|
66
68
|
},
|
|
69
|
+
activatePendingLiftedDragSession(targetSquare, mutationSession) {
|
|
70
|
+
return mutationSession.addMutation('state.interaction.activatePendingLiftedDragSession', interactionActivatePendingLiftedDragSession(internalState, targetSquare));
|
|
71
|
+
},
|
|
67
72
|
updateDragSessionCurrentTarget(sq, mutationSession) {
|
|
68
73
|
assert(internalState.dragSession, 'There must be an active drag session to update its current target');
|
|
69
74
|
return mutationSession.addMutation('state.interaction.updateDragSessionCurrentTarget', interactionUpdateDragSessionCurrentTarget(internalState, sq));
|
|
@@ -78,6 +83,13 @@ export function createInteractionState(options) {
|
|
|
78
83
|
clearActive(mutationSession) {
|
|
79
84
|
return mutationSession.addMutation('state.interaction.clearActive', interactionClearActive(internalState));
|
|
80
85
|
},
|
|
86
|
+
setConfig(config, mutationSession) {
|
|
87
|
+
const nextConfig = normalizeInteractionConfig(config, internalState.config);
|
|
88
|
+
return mutationSession.addMutation('state.interaction.setConfig', interactionSetConfig(internalState, nextConfig));
|
|
89
|
+
},
|
|
90
|
+
getConfig() {
|
|
91
|
+
return cloneDeep(internalState.config);
|
|
92
|
+
},
|
|
81
93
|
getSnapshot() {
|
|
82
94
|
return cloneDeep(internalState);
|
|
83
95
|
}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { InteractionStateMutationSession } from './mutation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { DragSessionSnapshot } from './types/internal.js';
|
|
3
3
|
import { InteractionStateInternal, InteractionStateSelected } from './types/main.js';
|
|
4
4
|
export declare function selectedEqual(a: InteractionStateSelected | null, b: InteractionStateSelected | null): boolean;
|
|
5
5
|
export declare function updateActiveDestinations(state: InteractionStateInternal, mutationSession: InteractionStateMutationSession): boolean;
|
|
6
|
-
export declare function isDragSessionCoreOwned(session:
|
|
6
|
+
export declare function isDragSessionCoreOwned<T extends DragSessionSnapshot>(session: T): session is Extract<T, {
|
|
7
|
+
owner: 'core';
|
|
8
|
+
}>;
|
|
9
|
+
export declare function isDragSessionReleaseTargeting<T extends DragSessionSnapshot>(session: T): session is Extract<T, {
|
|
10
|
+
type: 'release-targeting';
|
|
11
|
+
}>;
|
|
12
|
+
export declare function isDragSessionLiftedPiece<T extends DragSessionSnapshot>(session: T): session is Extract<T, {
|
|
13
|
+
type: 'lifted-piece-drag';
|
|
14
|
+
}>;
|
|
15
|
+
export declare function isDragSessionPendingLiftedPiece<T extends DragSessionSnapshot>(session: T): session is Extract<T, {
|
|
16
|
+
type: 'lifted-piece-drag';
|
|
17
|
+
phase: 'pending';
|
|
18
|
+
}>;
|
|
19
|
+
export declare function isDragSessionActiveLiftedPiece<T extends DragSessionSnapshot>(session: T): session is Extract<T, {
|
|
20
|
+
type: 'lifted-piece-drag';
|
|
21
|
+
phase: 'active';
|
|
22
|
+
}>;
|
|
@@ -17,3 +17,15 @@ export function updateActiveDestinations(state, mutationSession) {
|
|
|
17
17
|
export function isDragSessionCoreOwned(session) {
|
|
18
18
|
return session.owner === 'core';
|
|
19
19
|
}
|
|
20
|
+
export function isDragSessionReleaseTargeting(session) {
|
|
21
|
+
return session.type === 'release-targeting';
|
|
22
|
+
}
|
|
23
|
+
export function isDragSessionLiftedPiece(session) {
|
|
24
|
+
return session.type === 'lifted-piece-drag';
|
|
25
|
+
}
|
|
26
|
+
export function isDragSessionPendingLiftedPiece(session) {
|
|
27
|
+
return isDragSessionLiftedPiece(session) && session.phase === 'pending';
|
|
28
|
+
}
|
|
29
|
+
export function isDragSessionActiveLiftedPiece(session) {
|
|
30
|
+
return isDragSessionLiftedPiece(session) && session.phase === 'active';
|
|
31
|
+
}
|
|
@@ -2,10 +2,12 @@ import type { MutationSession } from '../../mutation/types.js';
|
|
|
2
2
|
export type InteractionStateMutationPayloadByCause = {
|
|
3
3
|
'state.interaction.setSelectedSquare': undefined;
|
|
4
4
|
'state.interaction.setDragSession': undefined;
|
|
5
|
+
'state.interaction.activatePendingLiftedDragSession': undefined;
|
|
5
6
|
'state.interaction.updateDragSessionCurrentTarget': undefined;
|
|
6
7
|
'state.interaction.clear': undefined;
|
|
7
8
|
'state.interaction.clearActive': undefined;
|
|
8
9
|
'state.interaction.setMovability': undefined;
|
|
9
10
|
'state.interaction.updateActiveDestinations': undefined;
|
|
11
|
+
'state.interaction.setConfig': undefined;
|
|
10
12
|
};
|
|
11
13
|
export type InteractionStateMutationSession = MutationSession<InteractionStateMutationPayloadByCause>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { ReadonlyDeep } from 'type-fest';
|
|
2
2
|
import type { Square } from '../board/types/internal.js';
|
|
3
|
+
import type { InteractionConfig } from './types/config.js';
|
|
3
4
|
import { Movability, type DragSessionSnapshot, type MoveDestination } from './types/internal.js';
|
|
4
5
|
import { InteractionStateInternal, InteractionStateSelected } from './types/main.js';
|
|
5
6
|
export declare function interactionSetSelected(state: InteractionStateInternal, selected: InteractionStateSelected | null): boolean;
|
|
6
7
|
export declare function interactionSetMovability(state: InteractionStateInternal, m: Movability): boolean;
|
|
8
|
+
export declare function interactionSetConfig(state: InteractionStateInternal, config: InteractionConfig): boolean;
|
|
7
9
|
export declare function interactionSetActiveDestinations(state: InteractionStateInternal, dests: ReadonlyMap<Square, ReadonlyDeep<MoveDestination>>): boolean;
|
|
8
10
|
export declare function interactionSetDragSession(state: InteractionStateInternal, session: DragSessionSnapshot | null): boolean;
|
|
11
|
+
export declare function interactionActivatePendingLiftedDragSession(state: InteractionStateInternal, targetSquare: Square | null): boolean;
|
|
9
12
|
export declare function interactionUpdateDragSessionCurrentTarget(state: InteractionStateInternal, sq: Square | null): boolean;
|
|
10
13
|
export declare function interactionClear(state: InteractionStateInternal): boolean;
|
|
11
14
|
export declare function interactionClearActive(state: InteractionStateInternal): boolean;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import assert from '@ktarmyshov/assert';
|
|
2
2
|
import { cloneDeep } from 'es-toolkit/object';
|
|
3
|
+
import { isEqual } from 'es-toolkit/predicate';
|
|
3
4
|
import { setsEqual } from '../../helpers/util.js';
|
|
4
|
-
import {
|
|
5
|
+
import { areInteractionConfigsEqual } from './config.js';
|
|
6
|
+
import { isDragSessionPendingLiftedPiece, selectedEqual } from './helpers.js';
|
|
5
7
|
import { movabilitiesEqual } from './movability.js';
|
|
6
8
|
export function interactionSetSelected(state, selected) {
|
|
7
9
|
const notChanged = selectedEqual(state.selected, selected);
|
|
@@ -16,6 +18,12 @@ export function interactionSetMovability(state, m) {
|
|
|
16
18
|
state.movability = cloneDeep(m); // Defensive copy to prevent external mutations
|
|
17
19
|
return true;
|
|
18
20
|
}
|
|
21
|
+
export function interactionSetConfig(state, config) {
|
|
22
|
+
if (areInteractionConfigsEqual(state.config, config))
|
|
23
|
+
return false; // no-op
|
|
24
|
+
state.config = cloneDeep(config); // Defensive copy to prevent external mutations
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
19
27
|
function promotedTosEqual(a, b) {
|
|
20
28
|
if (a === b)
|
|
21
29
|
return true;
|
|
@@ -46,15 +54,22 @@ export function interactionSetActiveDestinations(state, dests) {
|
|
|
46
54
|
return true;
|
|
47
55
|
}
|
|
48
56
|
export function interactionSetDragSession(state, session) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!changed)
|
|
54
|
-
return false; // no-op
|
|
57
|
+
if (state.dragSession === session)
|
|
58
|
+
return false;
|
|
59
|
+
if (isEqual(state.dragSession, session))
|
|
60
|
+
return false;
|
|
55
61
|
state.dragSession = session ? cloneDeep(session) : null;
|
|
56
62
|
return true;
|
|
57
63
|
}
|
|
64
|
+
export function interactionActivatePendingLiftedDragSession(state, targetSquare) {
|
|
65
|
+
const current = state.dragSession;
|
|
66
|
+
assert(current !== null, 'activatePendingLiftedDragSession requires an active drag session');
|
|
67
|
+
assert(isDragSessionPendingLiftedPiece(current), 'activatePendingLiftedDragSession requires a pending lifted-piece drag session');
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
const { startPoint: _startPoint, thresholdPx: _thresholdPx, ...rest } = current;
|
|
70
|
+
state.dragSession = { ...rest, phase: 'active', targetSquare };
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
58
73
|
export function interactionUpdateDragSessionCurrentTarget(state, sq) {
|
|
59
74
|
const changed = state.dragSession?.targetSquare !== sq;
|
|
60
75
|
if (!changed)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { PartialDeep } from 'type-fest';
|
|
1
2
|
import { MoveRequestInput, RolePromotionInput, SquareString } from '../../board/types/input.js';
|
|
3
|
+
import { InteractionConfig } from './config.js';
|
|
2
4
|
export interface MoveDestinationInput extends Omit<MoveRequestInput, 'from' | 'promotedTo'> {
|
|
3
5
|
promotedTo?: RolePromotionInput[];
|
|
4
6
|
}
|
|
@@ -16,3 +18,4 @@ export type DisabledMovabilityInput = {
|
|
|
16
18
|
mode: 'disabled';
|
|
17
19
|
};
|
|
18
20
|
export type MovabilityInput = StrictMovabilityInput | FreeMovabilityInput | DisabledMovabilityInput;
|
|
21
|
+
export type InteractionConfigInput = PartialDeep<InteractionConfig>;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import type { ReadonlyDeep } from 'type-fest';
|
|
2
|
-
import type { ExtensionDragSession, ExtensionDragSessionBase } from '../../../extensions/types/basic/interaction.js';
|
|
2
|
+
import type { ExtensionDragSession, ExtensionDragSessionActiveLiftedPiece, ExtensionDragSessionBase, ExtensionDragSessionPendingLiftedPiece } from '../../../extensions/types/basic/interaction.js';
|
|
3
3
|
import type { MoveRequest, PieceCode, RolePromotionCode, Square } from '../../board/types/internal.js';
|
|
4
|
+
import type { InteractionConfig } from './config.js';
|
|
4
5
|
export type DragSessionExtensionOwned = ExtensionDragSession & {
|
|
5
6
|
owner: string;
|
|
6
7
|
};
|
|
7
8
|
export type DragSessionExtensionOwnedSnapshot = ReadonlyDeep<DragSessionExtensionOwned>;
|
|
8
|
-
export interface
|
|
9
|
+
export interface DragSessionPendingLiftedPieceCoreOwned extends ExtensionDragSessionPendingLiftedPiece {
|
|
9
10
|
owner: 'core';
|
|
10
|
-
|
|
11
|
+
sourceSquare: Square;
|
|
12
|
+
}
|
|
13
|
+
export interface DragSessionActiveLiftedPieceCoreOwned extends ExtensionDragSessionActiveLiftedPiece {
|
|
14
|
+
owner: 'core';
|
|
15
|
+
sourceSquare: Square;
|
|
16
|
+
}
|
|
17
|
+
export interface DragSessionReleaseTargetingCoreOwned extends ExtensionDragSessionBase {
|
|
18
|
+
owner: 'core';
|
|
19
|
+
type: 'release-targeting';
|
|
11
20
|
sourceSquare: Square;
|
|
12
21
|
sourcePieceCode: PieceCode;
|
|
13
|
-
targetSquare: Square | null;
|
|
14
22
|
}
|
|
23
|
+
export type DragSessionCoreOwned = DragSessionPendingLiftedPieceCoreOwned | DragSessionActiveLiftedPieceCoreOwned | DragSessionReleaseTargetingCoreOwned;
|
|
15
24
|
export type DragSessionCoreOwnedSnapshot = ReadonlyDeep<DragSessionCoreOwned>;
|
|
16
25
|
export type DragSession = DragSessionCoreOwned | DragSessionExtensionOwned;
|
|
17
26
|
export type DragSessionSnapshot = ReadonlyDeep<DragSession>;
|
|
@@ -41,3 +50,4 @@ export interface MovabilityDisabled {
|
|
|
41
50
|
}
|
|
42
51
|
export type Movability = MovabilityStrict | MovabilityFree | MovabilityDisabled;
|
|
43
52
|
export type MovabilitySnapshot = ReadonlyDeep<Movability>;
|
|
53
|
+
export type InteractionConfigSnapshot = ReadonlyDeep<InteractionConfig>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { ReadonlyDeep } from 'type-fest';
|
|
2
|
-
import { NonEmptyPieceCode, Square } from '../../board/types/internal.js';
|
|
3
|
-
import { InteractionStateMutationSession } from '../mutation.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import type { ReadonlyDeep } from 'type-fest';
|
|
2
|
+
import type { NonEmptyPieceCode, Square } from '../../board/types/internal.js';
|
|
3
|
+
import type { InteractionStateMutationSession } from '../mutation.js';
|
|
4
|
+
import type { InteractionConfig } from './config.js';
|
|
5
|
+
import type { InteractionConfigInput, MovabilityInput } from './input.js';
|
|
6
|
+
import type { DragSession, DragSessionSnapshot, InteractionConfigSnapshot, Movability, MovabilitySnapshot, MoveDestinationSnapshot } from './internal.js';
|
|
6
7
|
export interface InteractionStateSelected {
|
|
7
8
|
square: Square;
|
|
8
9
|
pieceCode: NonEmptyPieceCode;
|
|
@@ -12,10 +13,12 @@ export interface InteractionStateInternal {
|
|
|
12
13
|
movability: Movability;
|
|
13
14
|
activeDestinations: ReadonlyMap<Square, MoveDestinationSnapshot>;
|
|
14
15
|
dragSession: DragSession | null;
|
|
16
|
+
config: InteractionConfig;
|
|
15
17
|
}
|
|
16
18
|
export type InteractionStateSnapshot = ReadonlyDeep<InteractionStateInternal>;
|
|
17
19
|
export interface InteractionStateInitOptions {
|
|
18
20
|
movability?: MovabilityInput;
|
|
21
|
+
config?: InteractionConfigInput;
|
|
19
22
|
}
|
|
20
23
|
export interface InteractionState {
|
|
21
24
|
readonly selected: InteractionStateSelected | null;
|
|
@@ -26,8 +29,11 @@ export interface InteractionState {
|
|
|
26
29
|
updateActiveDestinations(mutationSession: InteractionStateMutationSession): boolean;
|
|
27
30
|
readonly dragSession: DragSessionSnapshot | null;
|
|
28
31
|
setDragSession(session: DragSessionSnapshot | null, mutationSession: InteractionStateMutationSession): boolean;
|
|
32
|
+
activatePendingLiftedDragSession(targetSquare: Square | null, mutationSession: InteractionStateMutationSession): boolean;
|
|
29
33
|
updateDragSessionCurrentTarget(sq: Square | null, mutationSession: InteractionStateMutationSession): boolean;
|
|
30
34
|
clear(mutationSession: InteractionStateMutationSession): boolean;
|
|
31
35
|
clearActive(mutationSession: InteractionStateMutationSession): boolean;
|
|
36
|
+
setConfig(config: InteractionConfigInput, mutationSession: InteractionStateMutationSession): boolean;
|
|
37
|
+
getConfig(): InteractionConfigSnapshot;
|
|
32
38
|
getSnapshot(): InteractionStateSnapshot;
|
|
33
39
|
}
|
package/dist/wrapper/factory.js
CHANGED
|
@@ -34,6 +34,8 @@ export function createBoard(options) {
|
|
|
34
34
|
setOrientation: runtime.setOrientation,
|
|
35
35
|
setMovability: runtime.setMovability,
|
|
36
36
|
select: runtime.select,
|
|
37
|
+
setInteractionConfig: runtime.setInteractionConfig,
|
|
38
|
+
getInteractionConfig: runtime.getInteractionConfig,
|
|
37
39
|
getSnapshot: runtime.getSnapshot,
|
|
38
40
|
extensions: runtime.getExtensionsPublicRecord()
|
|
39
41
|
};
|
package/dist/wrapper/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type ChessboardInitOptions<TExtensions extends readonly ChessboardExtensi
|
|
|
51
51
|
/**
|
|
52
52
|
* Public board interface returned by createBoard(...)
|
|
53
53
|
*/
|
|
54
|
-
export type ChessboardRuntimeSurface = Pick<Runtime, 'destroy' | 'move' | 'setPosition' | 'setPiecePosition' | 'setTurn' | 'setOrientation' | 'setMovability' | 'select' | 'getSnapshot'>;
|
|
54
|
+
export type ChessboardRuntimeSurface = Pick<Runtime, 'destroy' | 'move' | 'setPosition' | 'setPiecePosition' | 'setTurn' | 'setOrientation' | 'setMovability' | 'select' | 'setInteractionConfig' | 'getInteractionConfig' | 'getSnapshot'>;
|
|
55
55
|
export interface Chessboard<TExtensions extends readonly ChessboardExtensionInput[] = DefaultBuiltinChessboardExtensions> extends ChessboardRuntimeSurface {
|
|
56
56
|
readonly extensions: ChessboardExtensionsPublicMap<TExtensions>;
|
|
57
57
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirasen/chessboard",
|
|
3
3
|
"description": "Framework-agnostic TypeScript chessboard platform with real chess interaction built in.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
|
-
"name": "Mirasen"
|
|
8
|
-
"email": "konstantin@mirasen.io"
|
|
7
|
+
"name": "Mirasen"
|
|
9
8
|
},
|
|
10
9
|
"keywords": [
|
|
11
10
|
"mirasen",
|
|
@@ -63,8 +62,8 @@
|
|
|
63
62
|
"prepack": "publint",
|
|
64
63
|
"format": "prettier --write .",
|
|
65
64
|
"lint": "prettier --check . && eslint .",
|
|
66
|
-
"check": "tsc --
|
|
67
|
-
"check:test": "tsc --project tsconfig-test.json
|
|
65
|
+
"check": "tsc --project tsconfig.json --noEmit",
|
|
66
|
+
"check:test": "tsc --project tsconfig-test.json",
|
|
68
67
|
"test": "npm run check:test && vitest run --run",
|
|
69
68
|
"coverage": "npm run check:test && vitest run --coverage",
|
|
70
69
|
"changeset:version": "changeset version && npm install && npm run format && git add --all",
|
|
@@ -97,7 +96,7 @@
|
|
|
97
96
|
"publint": "^0.3.11",
|
|
98
97
|
"rimraf": "^6.0.1",
|
|
99
98
|
"serve": "^14.2.6",
|
|
100
|
-
"typescript": "^
|
|
99
|
+
"typescript": "^6.0.3",
|
|
101
100
|
"typescript-eslint": "^8.57.0",
|
|
102
101
|
"vitest": "^4.0.18"
|
|
103
102
|
},
|
|
File without changes
|