@mirasen/chessboard 0.1.1 → 1.0.0-rc.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +224 -47
  3. package/dist/animation/planner.d.ts +11 -6
  4. package/dist/animation/planner.js +164 -45
  5. package/dist/animation/render/fade.d.ts +2 -2
  6. package/dist/animation/render/fade.js +3 -3
  7. package/dist/animation/render/move.d.ts +3 -3
  8. package/dist/animation/render/move.js +5 -5
  9. package/dist/animation/render/plan.d.ts +3 -3
  10. package/dist/animation/render/static.d.ts +2 -2
  11. package/dist/animation/render/static.js +3 -3
  12. package/dist/animation/types.d.ts +14 -15
  13. package/dist/animation/types.js +1 -6
  14. package/dist/extensions/factory/main.js +23 -2
  15. package/dist/extensions/factory/runtime.d.ts +2 -1
  16. package/dist/extensions/factory/runtime.js +48 -4
  17. package/dist/extensions/first-party/active-target/factory.js +5 -5
  18. package/dist/extensions/first-party/auto-promote/factory.d.ts +2 -0
  19. package/dist/extensions/first-party/auto-promote/factory.js +51 -0
  20. package/dist/extensions/first-party/auto-promote/types.d.ts +14 -0
  21. package/dist/extensions/first-party/auto-promote/types.js +2 -0
  22. package/dist/extensions/first-party/board-events/factory.js +1 -16
  23. package/dist/extensions/first-party/board-events/types.d.ts +1 -2
  24. package/dist/extensions/first-party/last-move/factory.js +6 -6
  25. package/dist/extensions/first-party/legal-moves/factory.js +1 -1
  26. package/dist/extensions/first-party/main-renderer/animation/render.js +1 -2
  27. package/dist/extensions/first-party/main-renderer/animation/update.js +9 -23
  28. package/dist/extensions/first-party/main-renderer/board/render.js +3 -3
  29. package/dist/extensions/first-party/main-renderer/board/update.js +5 -3
  30. package/dist/extensions/first-party/main-renderer/coordinates/render.js +4 -4
  31. package/dist/extensions/first-party/main-renderer/drag/render.js +1 -1
  32. package/dist/extensions/first-party/main-renderer/normalize.d.ts +2 -3
  33. package/dist/extensions/first-party/main-renderer/normalize.js +1 -1
  34. package/dist/extensions/first-party/main-renderer/pieces/render.js +5 -5
  35. package/dist/extensions/first-party/main-renderer/pieces/suppress.js +7 -2
  36. package/dist/extensions/first-party/main-renderer/types/extension.d.ts +1 -1
  37. package/dist/extensions/first-party/main-renderer/types/extension.js +1 -1
  38. package/dist/extensions/first-party/promotion/factory.d.ts +3 -0
  39. package/dist/extensions/first-party/promotion/factory.js +219 -0
  40. package/dist/extensions/first-party/promotion/normalize.d.ts +3 -0
  41. package/dist/extensions/first-party/promotion/normalize.js +37 -0
  42. package/dist/extensions/first-party/promotion/types/input.d.ts +10 -0
  43. package/dist/extensions/first-party/promotion/types/internal.d.ts +12 -0
  44. package/dist/extensions/first-party/promotion/types/internal.js +22 -0
  45. package/dist/extensions/first-party/promotion/types/main.d.ts +24 -0
  46. package/dist/extensions/first-party/promotion/types/main.js +2 -0
  47. package/dist/extensions/first-party/selected-square/factory.js +3 -3
  48. package/dist/extensions/index.d.ts +1 -1
  49. package/dist/extensions/types/basic/events.d.ts +8 -31
  50. package/dist/extensions/types/basic/events.js +9 -5
  51. package/dist/extensions/types/basic/interaction.d.ts +21 -0
  52. package/dist/extensions/types/basic/transient-visuals.d.ts +2 -1
  53. package/dist/extensions/types/context/events.d.ts +5 -0
  54. package/dist/extensions/types/context/events.js +1 -0
  55. package/dist/extensions/types/context/ui-move.d.ts +3 -15
  56. package/dist/extensions/types/extension.d.ts +10 -8
  57. package/dist/extensions/types/main.d.ts +11 -6
  58. package/dist/extensions/types/surface/commands.d.ts +9 -0
  59. package/dist/extensions/types/surface/events.d.ts +2 -3
  60. package/dist/extensions/types/wrapper.d.ts +23 -22
  61. package/dist/extensions/types/wrapper.js +34 -6
  62. package/dist/extensions/update.d.ts +2 -2
  63. package/dist/extensions/update.js +1 -1
  64. package/dist/layout/factory.js +3 -3
  65. package/dist/layout/geometry/factory.d.ts +2 -11
  66. package/dist/layout/geometry/factory.js +16 -17
  67. package/dist/layout/geometry/helpers.d.ts +5 -0
  68. package/dist/layout/geometry/helpers.js +33 -0
  69. package/dist/layout/geometry/types.d.ts +13 -12
  70. package/dist/layout/helpers.d.ts +3 -4
  71. package/dist/layout/helpers.js +5 -5
  72. package/dist/layout/reducers.js +9 -8
  73. package/dist/layout/types.d.ts +5 -5
  74. package/dist/render/mount.js +2 -2
  75. package/dist/render/rendering/state.js +7 -7
  76. package/dist/runtime/factory/events.d.ts +3 -0
  77. package/dist/runtime/factory/events.js +18 -0
  78. package/dist/runtime/factory/helpers.js +12 -12
  79. package/dist/runtime/factory/input.js +17 -10
  80. package/dist/runtime/factory/main.js +44 -4
  81. package/dist/runtime/input/adapter/event.d.ts +3 -0
  82. package/dist/runtime/input/adapter/event.js +19 -0
  83. package/dist/runtime/input/adapter/factory.js +19 -38
  84. package/dist/runtime/input/adapter/helpers.d.ts +3 -5
  85. package/dist/runtime/input/adapter/helpers.js +60 -12
  86. package/dist/runtime/input/adapter/pointer.d.ts +6 -10
  87. package/dist/runtime/input/adapter/pointer.js +37 -70
  88. package/dist/runtime/input/adapter/types.d.ts +6 -3
  89. package/dist/runtime/input/adapter/types.js +6 -1
  90. package/dist/runtime/input/controller/factory.js +12 -11
  91. package/dist/runtime/input/controller/pointer.d.ts +5 -6
  92. package/dist/runtime/input/controller/pointer.js +36 -35
  93. package/dist/runtime/input/controller/transient-visuals.d.ts +2 -2
  94. package/dist/runtime/input/controller/transient-visuals.js +12 -5
  95. package/dist/runtime/input/controller/types.d.ts +5 -4
  96. package/dist/runtime/lifecycle.js +6 -6
  97. package/dist/runtime/mutation/interaction.js +1 -5
  98. package/dist/runtime/mutation/types.d.ts +10 -8
  99. package/dist/runtime/types/main.d.ts +2 -0
  100. package/dist/state/change/{factory.d.ts → factory/main.d.ts} +1 -1
  101. package/dist/state/change/{factory.js → factory/main.js} +8 -5
  102. package/dist/state/change/factory/ui-move.d.ts +4 -0
  103. package/dist/{runtime → state/change}/factory/ui-move.js +9 -9
  104. package/dist/state/change/helpers.d.ts +4 -2
  105. package/dist/state/change/helpers.js +11 -1
  106. package/dist/state/change/mutation.d.ts +1 -1
  107. package/dist/state/change/reducers.d.ts +3 -3
  108. package/dist/state/change/reducers.js +4 -4
  109. package/dist/state/change/types/main.d.ts +18 -0
  110. package/dist/state/change/types/main.js +1 -0
  111. package/dist/state/change/types/ui-move.d.ts +25 -0
  112. package/dist/state/change/types/ui-move.js +1 -0
  113. package/dist/state/factory.js +1 -1
  114. package/dist/state/interaction/factory.js +4 -2
  115. package/dist/state/interaction/helpers.d.ts +2 -0
  116. package/dist/state/interaction/helpers.js +3 -0
  117. package/dist/state/interaction/types/internal.d.ts +9 -1
  118. package/dist/state/types.d.ts +1 -1
  119. package/dist/wrapper/factory.js +2 -26
  120. package/package.json +9 -5
  121. package/dist/runtime/factory/ui-move.d.ts +0 -4
  122. package/dist/runtime/types/ui-move.d.ts +0 -9
  123. package/dist/state/change/types.d.ts +0 -17
  124. /package/dist/{runtime/types/ui-move.js → extensions/first-party/promotion/types/input.js} +0 -0
  125. /package/dist/{state/change/types.js → extensions/types/basic/interaction.js} +0 -0
@@ -1,38 +1,42 @@
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';
4
5
  import { canMoveTo } from './helpers.js';
5
- export function handlePointerDown(state, event) {
6
- assert(event.type === 'pointerdown', 'handlePointerDown should only be called for pointerdown events');
7
- if (event.button !== 0) {
6
+ export function handlePointerDown(state, context) {
7
+ assert(context.rawEvent.type === 'pointerdown', 'handlePointerDown should only be called for pointerdown events');
8
+ const rawEvent = context.rawEvent;
9
+ if (rawEvent.button !== 0) {
8
10
  // Only handle left-click for now
9
11
  return;
10
12
  }
13
+ const sceneEvent = context.sceneEvent;
14
+ assert(sceneEvent, 'Scene event should be present for pointer events');
11
15
  const interaction = state.surface.getInteractionStateSnapshot();
12
16
  if (interaction.dragSession) {
13
17
  // Ignore pointer down events if there's already an active drag session
14
18
  return;
15
19
  }
16
- if (event.target !== null) {
20
+ if (sceneEvent.targetSquare !== null) {
17
21
  /**
18
22
  * If piece is already selected, and we have a valid target square event.target
19
23
  * AND it's a different square AND (it's empty OR (has an opponent piece AND is a legal move target))
20
24
  * THEN start a release-targeting drag session
21
25
  */
22
- if (interaction.selected && event.target !== interaction.selected.square) {
26
+ if (interaction.selected && sceneEvent.targetSquare !== interaction.selected.square) {
23
27
  const selectedPieceCode = interaction.selected.pieceCode;
24
28
  assert(isNonEmptyPieceCode(selectedPieceCode), 'Selected piece code must be non-zero');
25
- const targetPieceCode = state.surface.getPieceCodeAt(event.target);
29
+ const targetPieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
26
30
  if (isEmptyPieceCode(targetPieceCode)) {
27
- state.surface.startReleaseTargetingDrag(interaction.selected.square, event.target);
31
+ state.surface.startReleaseTargetingDrag(interaction.selected.square, sceneEvent.targetSquare);
28
32
  return;
29
33
  }
30
34
  const isLegalMoveTarget = interaction.movability.mode === 1 /* MovabilityModeCode.Free */ ||
31
35
  (interaction.movability.mode === 2 /* MovabilityModeCode.Strict */ &&
32
- interaction.activeDestinations.has(event.target));
36
+ interaction.activeDestinations.has(sceneEvent.targetSquare));
33
37
  if (fromPieceCode(targetPieceCode).color !== fromPieceCode(selectedPieceCode).color &&
34
38
  isLegalMoveTarget) {
35
- state.surface.startReleaseTargetingDrag(interaction.selected.square, event.target);
39
+ state.surface.startReleaseTargetingDrag(interaction.selected.square, sceneEvent.targetSquare);
36
40
  return;
37
41
  }
38
42
  }
@@ -40,53 +44,57 @@ export function handlePointerDown(state, event) {
40
44
  * If we are here, then it's not release-targeting.
41
45
  * So it's either a new lift or re-lift of the same piece. In either case, we can just start a lifted drag session if the target is valid.
42
46
  */
43
- const pieceCode = state.surface.getPieceCodeAt(event.target);
47
+ const pieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
44
48
  if (!isEmptyPieceCode(pieceCode)) {
45
- state.surface.startLiftedDrag(event.target, event.target);
49
+ state.surface.startLiftedDrag(sceneEvent.targetSquare, sceneEvent.targetSquare);
46
50
  }
47
51
  }
48
52
  }
49
- export function handlePointerMove(state, event) {
50
- assert(event.type === 'pointermove', 'handlePointerMove should only be called for pointermove events');
53
+ export function handlePointerMove(state, context) {
54
+ assert(context.rawEvent.type === 'pointermove', 'handlePointerMove should only be called for pointermove events');
51
55
  const interaction = state.surface.getInteractionStateSnapshot();
52
56
  if (interaction.dragSession) {
53
- state.surface.updateDragSessionCurrentTarget(event.target);
57
+ state.surface.updateDragSessionCurrentTarget(context.sceneEvent?.targetSquare ?? null);
54
58
  }
55
59
  }
56
- export function handlePointerUp(state, event) {
57
- assert(event.type === 'pointerup', 'handlePointerUp should only be called for pointerup events');
60
+ export function handlePointerUp(state, context) {
61
+ assert(context.rawEvent.type === 'pointerup', 'handlePointerUp should only be called for pointerup events');
58
62
  const interaction = state.surface.getInteractionStateSnapshot();
59
- if (!interaction.dragSession) {
63
+ const dragSession = interaction.dragSession;
64
+ if (!dragSession) {
60
65
  // No active drag session, so nothing to do on pointer up
61
66
  return;
62
67
  }
68
+ const sceneEvent = context.sceneEvent;
69
+ assert(sceneEvent, 'Scene event should be present for pointer events');
70
+ if (!isDragSessionCoreOwned(dragSession)) {
71
+ // For extension-owned drag sessions, we simply end the session without attempting to make a move,
72
+ // since the runtime doesn't have enough information about the semantics of the drag session.
73
+ state.surface.completeExtensionDrag(sceneEvent.targetSquare);
74
+ return;
75
+ }
63
76
  // Check if the square is the same as the source square of the drag session.
64
77
  // If it is, then we can end the drag session without making a move.
65
- if (event.target === interaction.dragSession.sourceSquare) {
78
+ if (sceneEvent.targetSquare === dragSession.sourceSquare) {
66
79
  state.surface.cancelActiveInteraction();
67
80
  return;
68
81
  }
69
82
  // Check if the target square is a valid destination for the selected piece.
70
- if (event.target !== null && canMoveTo(interaction, event.target)) {
71
- if (interaction.dragSession.type === 'lifted-piece-drag') {
72
- state.surface.dropTo(event.target);
73
- }
74
- else {
75
- state.surface.releaseTo(event.target);
76
- }
83
+ if (sceneEvent.targetSquare !== null && canMoveTo(interaction, sceneEvent.targetSquare)) {
84
+ state.surface.completeCoreDragTo(sceneEvent.targetSquare);
77
85
  return;
78
86
  }
79
87
  // Invalid target: piece returns to source for lifted drag (selection preserved),
80
88
  // or selection is cleared for release targeting.
81
- if (interaction.dragSession.type === 'lifted-piece-drag') {
89
+ if (dragSession.type === 'lifted-piece-drag') {
82
90
  state.surface.cancelActiveInteraction();
83
91
  }
84
92
  else {
85
93
  state.surface.cancelInteraction();
86
94
  }
87
95
  }
88
- export function handlePointerCancel(state, event) {
89
- assert(event.type === 'pointercancel', 'handlePointerCancel should only be called for pointercancel events');
96
+ export function handlePointerCancel(state, context) {
97
+ assert(context.rawEvent.type === 'pointercancel', 'handlePointerCancel should only be called for pointercancel events');
90
98
  const interaction = state.surface.getInteractionStateSnapshot();
91
99
  if (!interaction.dragSession) {
92
100
  // No active drag session, so nothing to do on pointer cancel
@@ -95,10 +103,3 @@ export function handlePointerCancel(state, event) {
95
103
  // Cancel the active interaction on pointer cancel
96
104
  state.surface.cancelActiveInteraction();
97
105
  }
98
- export function handlePointerLeave(state, event) {
99
- assert(event.type === 'pointerleave', 'handlePointerLeave should only be called for pointerleave events');
100
- const interaction = state.surface.getInteractionStateSnapshot();
101
- if (interaction.dragSession) {
102
- state.surface.updateDragSessionCurrentTarget(event.target);
103
- }
104
- }
@@ -1,3 +1,3 @@
1
- import { SceneEvent } from '../../../extensions/types/basic/events.js';
1
+ import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
2
2
  import { InteractionControllerInternal } from './types.js';
3
- export declare function transmitTransientInput(state: InteractionControllerInternal, event: SceneEvent): void;
3
+ export declare function transmitTransientInput(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
@@ -1,12 +1,19 @@
1
1
  function isPointerEvent(event) {
2
2
  return event.type.startsWith('pointer');
3
3
  }
4
- export function transmitTransientInput(state, event) {
5
- if (isPointerEvent(event) && event.rawPoint && event.clampedPoint) {
4
+ function isRawPointerEvent(event) {
5
+ return event instanceof PointerEvent;
6
+ }
7
+ export function transmitTransientInput(state, context) {
8
+ if (context.sceneEvent &&
9
+ isPointerEvent(context.sceneEvent) &&
10
+ isRawPointerEvent(context.rawEvent) &&
11
+ context.sceneEvent.boardClampedPoint) {
6
12
  state.surface.transientInput({
7
- target: event.target,
8
- rawPoint: event.rawPoint,
9
- clampedPoint: event.clampedPoint
13
+ target: context.sceneEvent.targetSquare,
14
+ point: context.sceneEvent.point,
15
+ clampedPoint: context.sceneEvent.clampedPoint,
16
+ boardClampedPoint: context.sceneEvent.boardClampedPoint
10
17
  });
11
18
  }
12
19
  }
@@ -1,18 +1,19 @@
1
- import { SceneEvent } from '../../../extensions/types/basic/events.js';
2
1
  import { TransientInput } from '../../../extensions/types/basic/transient-visuals.js';
2
+ import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
3
3
  import { PieceCode, Square } from '../../../state/board/types/internal.js';
4
4
  import { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
5
5
  export interface RuntimeInteractionSurface {
6
6
  getInteractionStateSnapshot(): InteractionStateSnapshot;
7
7
  getPieceCodeAt(square: Square): PieceCode;
8
8
  startLiftedDrag(source: Square, target: Square): void;
9
- dropTo(target: Square): void;
10
9
  startReleaseTargetingDrag(source: Square, target: Square): void;
11
- releaseTo(target: Square): void;
10
+ completeCoreDragTo(target: Square): void;
11
+ completeExtensionDrag(target: Square | null): void;
12
12
  updateDragSessionCurrentTarget(target: Square | null): void;
13
13
  cancelActiveInteraction(): void;
14
14
  cancelInteraction(): void;
15
15
  transientInput(input: TransientInput): void;
16
+ onEvent(context: ExtensionOnEventContext): void;
16
17
  }
17
18
  export interface InteractionControllerInternal {
18
19
  readonly surface: RuntimeInteractionSurface;
@@ -21,5 +22,5 @@ export interface InteractionControllerInitOptions {
21
22
  surface: RuntimeInteractionSurface;
22
23
  }
23
24
  export interface InteractionController {
24
- onEvent(event: SceneEvent): void;
25
+ onEvent(context: ExtensionOnEventContext): void;
25
26
  }
@@ -16,18 +16,18 @@ export function runtimeValidateIsNotMounted(state) {
16
16
  }
17
17
  export function runtimeMount(state, container) {
18
18
  runtimeValidateIsNotMounted(state);
19
+ assert(state.inputAdapter === null, 'Input adapter should not be initialized before mounting');
20
+ state.inputAdapter = createInputAdapter({
21
+ container,
22
+ getRenderGeometry: () => state.layout.geometry,
23
+ controller: state.interactionController
24
+ });
19
25
  state.renderSystem.mount(container);
20
26
  state.resizeObserver = new ResizeObserver(() => {
21
27
  runtimeRefreshGeometry(state);
22
28
  });
23
29
  // Observe will cause immediate first callback!
24
30
  state.resizeObserver.observe(container);
25
- assert(state.inputAdapter === null, 'Input adapter should not be initialized before mounting');
26
- state.inputAdapter = createInputAdapter({
27
- container,
28
- getGeometry: () => state.layout.geometry,
29
- controller: state.interactionController
30
- });
31
31
  }
32
32
  export function runtimeUnmount(state) {
33
33
  runtimeValidateIsMounted(state);
@@ -4,11 +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: [
8
- 'state.interaction.clear',
9
- 'runtime.interaction.dropTo',
10
- 'runtime.interaction.releaseTo'
11
- ]
7
+ causes: ['state.interaction.clear', 'runtime.interaction.completeDragTo']
12
8
  })) {
13
9
  return;
14
10
  }
@@ -1,12 +1,14 @@
1
- import { LayoutMutationPayloadByCause } from '../../layout/mutation.js';
2
- import { MutationSession, ReadonlyMutationSession } from '../../mutation/types.js';
3
- import { BoardStateMutationPayloadByCause } from '../../state/board/mutation.js';
4
- import { ChangeStateMutationPayloadByCause } from '../../state/change/mutation.js';
5
- import { InteractionStateMutationPayloadByCause } from '../../state/interaction/mutation.js';
6
- import { ViewStateMutationPayloadByCause } from '../../state/view/mutation.js';
1
+ import type { LayoutMutationPayloadByCause } from '../../layout/mutation.js';
2
+ import type { MutationSession, ReadonlyMutationSession } from '../../mutation/types.js';
3
+ import type { BoardStateMutationPayloadByCause } from '../../state/board/mutation.js';
4
+ import type { ChangeStateMutationPayloadByCause } from '../../state/change/mutation.js';
5
+ import type { InteractionStateMutationPayloadByCause } from '../../state/interaction/mutation.js';
6
+ import type { DragSessionCoreOwnedSnapshot } from '../../state/interaction/types/internal.js';
7
+ import type { ViewStateMutationPayloadByCause } from '../../state/view/mutation.js';
7
8
  type RuntimeOwnMutationPayloadByCause = {
8
- 'runtime.interaction.dropTo': undefined;
9
- 'runtime.interaction.releaseTo': undefined;
9
+ 'runtime.interaction.completeDragTo': DragSessionCoreOwnedSnapshot;
10
+ 'runtime.interaction.resolveDeferredUIMoveRequest': undefined;
11
+ 'runtime.interaction.cancelDeferredUIMoveRequest': undefined;
10
12
  };
11
13
  export type RuntimeMutationPayloadByCause = BoardStateMutationPayloadByCause & ChangeStateMutationPayloadByCause & InteractionStateMutationPayloadByCause & ViewStateMutationPayloadByCause & LayoutMutationPayloadByCause & RuntimeOwnMutationPayloadByCause;
12
14
  export type RuntimeMutationSession = MutationSession<RuntimeMutationPayloadByCause>;
@@ -1,5 +1,6 @@
1
1
  import { AnyExtensionDefinition } from '../../extensions/types/extension.js';
2
2
  import { ExtensionRuntimeSurfaceCommands } from '../../extensions/types/surface/commands.js';
3
+ import { ExtensionRuntimeSurfaceEvents } from '../../extensions/types/surface/events.js';
3
4
  import { RuntimeStateInitOptions } from '../../state/types.js';
4
5
  import { InputAdapter } from '../input/adapter/types.js';
5
6
  import { InteractionController } from '../input/controller/types.js';
@@ -19,6 +20,7 @@ export interface RuntimeInitOptions {
19
20
  }
20
21
  export interface RuntimeInitOptionsInternal extends RuntimeInitOptions {
21
22
  extensionRuntimeSurfaceCommands: ExtensionRuntimeSurfaceCommands;
23
+ extensionRuntimeSurfaceEvents: ExtensionRuntimeSurfaceEvents;
22
24
  getInternalState: GetInternalState;
23
25
  }
24
26
  /**
@@ -1,2 +1,2 @@
1
- import type { ChangeState } from './types.js';
1
+ import { ChangeState } from '../types/main.js';
2
2
  export declare function createChangeState(): ChangeState;
@@ -1,9 +1,9 @@
1
1
  import { cloneDeep } from 'es-toolkit/object';
2
- import { changeStateSetDeferredUIMoveRequestContext, changeStateSetLastMove } from './reducers.js';
2
+ import { changeStateSetDeferredUIMoveRequest, changeStateSetLastMove } from '../reducers.js';
3
3
  function createChangeStateInternal() {
4
4
  return {
5
5
  lastMove: null,
6
- deferredUIMoveRequestContext: null
6
+ deferredUIMoveRequest: null
7
7
  };
8
8
  }
9
9
  export function createChangeState() {
@@ -12,17 +12,20 @@ export function createChangeState() {
12
12
  get lastMove() {
13
13
  return cloneDeep(internalState.lastMove);
14
14
  },
15
+ get deferredUIMoveRequest() {
16
+ return cloneDeep(internalState.deferredUIMoveRequest);
17
+ },
15
18
  setLastMove(move, mutationSession) {
16
19
  return mutationSession.addMutation('state.change.setLastMove', changeStateSetLastMove(internalState, move));
17
20
  },
18
21
  getSnapshot() {
19
22
  return {
20
23
  ...cloneDeep(internalState),
21
- deferredUIMoveRequestContext: internalState.deferredUIMoveRequestContext?.getSnapshot() ?? null
24
+ deferredUIMoveRequest: internalState.deferredUIMoveRequest?.getSnapshot() ?? null
22
25
  };
23
26
  },
24
- setDeferredUIMoveRequestContext(context, mutationSession) {
25
- return mutationSession.addMutation('state.change.setDeferredUIMoveRequestContext', changeStateSetDeferredUIMoveRequestContext(internalState, context));
27
+ setDeferredUIMoveRequest(request, mutationSession) {
28
+ return mutationSession.addMutation('state.change.setDeferredUIMoveRequest', changeStateSetDeferredUIMoveRequest(internalState, request));
26
29
  }
27
30
  };
28
31
  }
@@ -0,0 +1,4 @@
1
+ import { Square } from '../../board/types/internal.js';
2
+ import { MoveDestinationSnapshot } from '../../interaction/types/internal.js';
3
+ import { PendingUIMoveRequest } from '../types/ui-move.js';
4
+ export declare function createPendingUIMoveRequest(sourceSquare: Square, destination: MoveDestinationSnapshot): PendingUIMoveRequest;
@@ -1,4 +1,4 @@
1
- function createMoveRequestContextInternal(sourceSquare, destination) {
1
+ function createPendingUIMoveRequestInternal(sourceSquare, destination) {
2
2
  return {
3
3
  sourceSquare,
4
4
  destination,
@@ -6,7 +6,7 @@ function createMoveRequestContextInternal(sourceSquare, destination) {
6
6
  resolvedMoveRequest: null
7
7
  };
8
8
  }
9
- function moveRequestContextCanBeAutoResolved(state) {
9
+ function pendingUIMoveRequestCanBeAutoResolved(state) {
10
10
  return (state.status === 'unresolved' &&
11
11
  (state.destination.promotedTo === undefined || state.destination.promotedTo.length <= 1));
12
12
  }
@@ -21,11 +21,11 @@ function assertCanBeResolved(state) {
21
21
  }
22
22
  }
23
23
  function assertCanBeAutoResolved(state) {
24
- if (!moveRequestContextCanBeAutoResolved(state)) {
24
+ if (!pendingUIMoveRequestCanBeAutoResolved(state)) {
25
25
  throw new Error('Move request cannot be auto-resolved. Either it is already resolved or deferred, or it requires a promotion choice.');
26
26
  }
27
27
  }
28
- function moveRequestContextAutoResolve(state) {
28
+ function pendingUIMoveRequestAutoResolve(state) {
29
29
  assertCanBeAutoResolved(state);
30
30
  const promotedTo = state.destination.promotedTo?.length === 1 ? state.destination.promotedTo[0] : undefined;
31
31
  state.status = 'resolved';
@@ -41,8 +41,8 @@ function moveRequestContextAutoResolve(state) {
41
41
  ...(promotedTo !== undefined ? { promotedTo } : {})
42
42
  };
43
43
  }
44
- export function createUIMoveRequestContext(sourceSquare, destination) {
45
- const internalState = createMoveRequestContextInternal(sourceSquare, destination);
44
+ export function createPendingUIMoveRequest(sourceSquare, destination) {
45
+ const internalState = createPendingUIMoveRequestInternal(sourceSquare, destination);
46
46
  return {
47
47
  get status() {
48
48
  return internalState.status;
@@ -54,7 +54,7 @@ export function createUIMoveRequestContext(sourceSquare, destination) {
54
54
  return internalState.destination;
55
55
  },
56
56
  get canBeAutoResolved() {
57
- return moveRequestContextCanBeAutoResolved(internalState);
57
+ return pendingUIMoveRequestCanBeAutoResolved(internalState);
58
58
  },
59
59
  get resolvedMoveRequest() {
60
60
  return internalState.resolvedMoveRequest;
@@ -69,14 +69,14 @@ export function createUIMoveRequestContext(sourceSquare, destination) {
69
69
  internalState.resolvedMoveRequest = request;
70
70
  },
71
71
  autoresolve() {
72
- moveRequestContextAutoResolve(internalState);
72
+ pendingUIMoveRequestAutoResolve(internalState);
73
73
  },
74
74
  getSnapshot() {
75
75
  return {
76
76
  status: internalState.status,
77
77
  sourceSquare: internalState.sourceSquare,
78
78
  destination: internalState.destination,
79
- canBeAutoResolved: moveRequestContextCanBeAutoResolved(internalState),
79
+ canBeAutoResolved: pendingUIMoveRequestCanBeAutoResolved(internalState),
80
80
  resolvedMoveRequest: internalState.resolvedMoveRequest
81
81
  };
82
82
  }
@@ -1,8 +1,10 @@
1
- import { ExtensionUIMoveRequestContextSnapshot } from '../../extensions/types/context/ui-move.js';
2
1
  import type { MoveRequest, MoveRequestBase, MoveSnapshot } from '../board/types/internal.js';
3
2
  import { MoveDestinationSnapshot } from '../interaction/types/internal.js';
3
+ import { ChangeStateSnapshot } from './types/main.js';
4
+ import { PendingUIMoveRequestSnapshot } from './types/ui-move.js';
4
5
  export declare function movesEqual(moveA: MoveSnapshot | null, moveB: MoveSnapshot | null): boolean;
5
6
  export declare function moveRequestBasesEqual(requestA: MoveRequestBase | null, requestB: MoveRequestBase | null): boolean;
6
7
  export declare function moveRequestsEqual(requestA: MoveRequest | null, requestB: MoveRequest | null): boolean;
7
8
  export declare function moveDestinationsEqual(destA: MoveDestinationSnapshot | null, destB: MoveDestinationSnapshot | null): boolean;
8
- export declare function uiMoveRequestContextsEqual(contextA: ExtensionUIMoveRequestContextSnapshot | null, contextB: ExtensionUIMoveRequestContextSnapshot | null): boolean;
9
+ export declare function pendingUIMoveRequestsEqual(contextA: PendingUIMoveRequestSnapshot | null, contextB: PendingUIMoveRequestSnapshot | null): boolean;
10
+ export declare function changeStatesEqual(stateA: ChangeStateSnapshot | null, stateB: ChangeStateSnapshot | null): boolean;
@@ -69,7 +69,7 @@ export function moveDestinationsEqual(destA, destB) {
69
69
  setsEqual(new Set(destA.promotedTo ?? []), new Set(destB.promotedTo ?? [])) &&
70
70
  moveRequestBasesEqual(destA.secondary ?? null, destB.secondary ?? null));
71
71
  }
72
- export function uiMoveRequestContextsEqual(contextA, contextB) {
72
+ export function pendingUIMoveRequestsEqual(contextA, contextB) {
73
73
  if (contextA === null && contextB === null) {
74
74
  return true;
75
75
  }
@@ -84,3 +84,13 @@ export function uiMoveRequestContextsEqual(contextA, contextB) {
84
84
  contextA.canBeAutoResolved === contextB.canBeAutoResolved &&
85
85
  moveRequestsEqual(contextA.resolvedMoveRequest, contextB.resolvedMoveRequest));
86
86
  }
87
+ export function changeStatesEqual(stateA, stateB) {
88
+ if (stateA === null && stateB === null) {
89
+ return true;
90
+ }
91
+ if (stateA === null || stateB === null) {
92
+ return false;
93
+ }
94
+ return (pendingUIMoveRequestsEqual(stateA.deferredUIMoveRequest, stateB.deferredUIMoveRequest) &&
95
+ movesEqual(stateA.lastMove, stateB.lastMove));
96
+ }
@@ -1,6 +1,6 @@
1
1
  import type { MutationSession } from '../../mutation/types.js';
2
2
  export type ChangeStateMutationPayloadByCause = {
3
3
  'state.change.setLastMove': undefined;
4
- 'state.change.setDeferredUIMoveRequestContext': undefined;
4
+ 'state.change.setDeferredUIMoveRequest': undefined;
5
5
  };
6
6
  export type ChangeStateMutationSession = MutationSession<ChangeStateMutationPayloadByCause>;
@@ -1,5 +1,5 @@
1
- import { ExtensionUIMoveRequestContext } from '../../extensions/types/context/ui-move.js';
2
1
  import type { MoveSnapshot } from '../board/types/internal.js';
3
- import type { ChangeStateInternal } from './types.js';
2
+ import { ChangeStateInternal } from './types/main.js';
3
+ import { PendingUIMoveRequest } from './types/ui-move.js';
4
4
  export declare function changeStateSetLastMove(state: ChangeStateInternal, move: MoveSnapshot | null): boolean;
5
- export declare function changeStateSetDeferredUIMoveRequestContext(state: ChangeStateInternal, context: ExtensionUIMoveRequestContext): boolean;
5
+ export declare function changeStateSetDeferredUIMoveRequest(state: ChangeStateInternal, request: PendingUIMoveRequest | null): boolean;
@@ -1,5 +1,5 @@
1
1
  import { cloneDeep } from 'es-toolkit/object';
2
- import { movesEqual, uiMoveRequestContextsEqual } from './helpers.js';
2
+ import { movesEqual, pendingUIMoveRequestsEqual } from './helpers.js';
3
3
  export function changeStateSetLastMove(state, move) {
4
4
  if (movesEqual(state.lastMove, move)) {
5
5
  return false; // No change
@@ -7,10 +7,10 @@ export function changeStateSetLastMove(state, move) {
7
7
  state.lastMove = cloneDeep(move);
8
8
  return true;
9
9
  }
10
- export function changeStateSetDeferredUIMoveRequestContext(state, context) {
11
- if (uiMoveRequestContextsEqual(state.deferredUIMoveRequestContext, context)) {
10
+ export function changeStateSetDeferredUIMoveRequest(state, request) {
11
+ if (pendingUIMoveRequestsEqual(state.deferredUIMoveRequest, request)) {
12
12
  return false; // No change
13
13
  }
14
- state.deferredUIMoveRequestContext = context;
14
+ state.deferredUIMoveRequest = request;
15
15
  return true;
16
16
  }
@@ -0,0 +1,18 @@
1
+ import type { ReadonlyDeep } from 'type-fest';
2
+ import { MoveSnapshot } from '../../board/types/internal.js';
3
+ import type { ChangeStateMutationSession } from '../mutation.js';
4
+ import { PendingUIMoveRequest, PendingUIMoveRequestSnapshot } from './ui-move.js';
5
+ export interface ChangeStateInternal {
6
+ lastMove: MoveSnapshot | null;
7
+ deferredUIMoveRequest: PendingUIMoveRequest | null;
8
+ }
9
+ export type ChangeStateSnapshot = ReadonlyDeep<Omit<ChangeStateInternal, 'deferredUIMoveRequest'>> & {
10
+ deferredUIMoveRequest: PendingUIMoveRequestSnapshot | null;
11
+ };
12
+ export interface ChangeState {
13
+ readonly lastMove: MoveSnapshot | null;
14
+ readonly deferredUIMoveRequest: PendingUIMoveRequest | null;
15
+ setLastMove(move: MoveSnapshot | null, mutationSession: ChangeStateMutationSession): boolean;
16
+ setDeferredUIMoveRequest(request: PendingUIMoveRequest | null, mutationSession: ChangeStateMutationSession): boolean;
17
+ getSnapshot(): ChangeStateSnapshot;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { MoveRequest, RolePromotionCode, Square } from '../../board/types/internal.js';
2
+ import { MoveDestinationSnapshot } from '../../interaction/types/internal.js';
3
+ export type PendingUIMoveRequestStatus = 'unresolved' | 'deferred' | 'resolved';
4
+ export interface PendingUIMoveRequestSnapshot {
5
+ readonly status: PendingUIMoveRequestStatus;
6
+ readonly sourceSquare: Square;
7
+ readonly destination: MoveDestinationSnapshot;
8
+ readonly canBeAutoResolved: boolean;
9
+ readonly resolvedMoveRequest: MoveRequest | null;
10
+ }
11
+ export interface PendingUIMoveRequest extends PendingUIMoveRequestSnapshot {
12
+ defer(): void;
13
+ resolve(request: MoveRequest): void;
14
+ autoresolve(): void;
15
+ getSnapshot(): PendingUIMoveRequestSnapshot;
16
+ }
17
+ export interface PendingUIMoveRequestInternal {
18
+ readonly sourceSquare: Square;
19
+ readonly destination: MoveDestinationSnapshot;
20
+ status: PendingUIMoveRequestStatus;
21
+ resolvedMoveRequest: MoveRequest | null;
22
+ }
23
+ export interface DeferredUIMoveResolutionDetails {
24
+ readonly promotedTo: RolePromotionCode;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { createBoardState } from './board/factory.js';
2
- import { createChangeState } from './change/factory.js';
2
+ import { createChangeState } from './change/factory/main.js';
3
3
  import { createInteractionState } from './interaction/factory.js';
4
4
  import { createViewState } from './view/factory.js';
5
5
  function createRuntimeStateInternal(options) {
@@ -51,8 +51,10 @@ export function createInteractionState(options) {
51
51
  return internalState.dragSession ? { ...internalState.dragSession } : null;
52
52
  },
53
53
  setDragSession(session, mutationSession) {
54
- // Assert that the selected is the same as the source of the drag session when setting a lifted-piece-drag session
55
- if (session) {
54
+ // assert that there is no active session when setting a new session.
55
+ assert(session === null || internalState.dragSession === null, 'Cannot set a new drag session while another session is active');
56
+ if (session && session.owner === 'core') {
57
+ // Assert that the selected is the same as the source of the drag session when setting a lifted-piece-drag session
56
58
  assert(internalState.selected, 'There must be a selected piece to start a drag session');
57
59
  if (session.type === 'lifted-piece-drag') {
58
60
  assert(internalState.selected, 'There must be a selected piece to start a lifted-piece-drag session');
@@ -1,4 +1,6 @@
1
1
  import { InteractionStateMutationSession } from './mutation.js';
2
+ import { DragSessionCoreOwnedSnapshot, DragSessionSnapshot } from './types/internal.js';
2
3
  import { InteractionStateInternal, InteractionStateSelected } from './types/main.js';
3
4
  export declare function selectedEqual(a: InteractionStateSelected | null, b: InteractionStateSelected | null): boolean;
4
5
  export declare function updateActiveDestinations(state: InteractionStateInternal, mutationSession: InteractionStateMutationSession): boolean;
6
+ export declare function isDragSessionCoreOwned(session: DragSessionSnapshot): session is DragSessionCoreOwnedSnapshot;
@@ -14,3 +14,6 @@ export function updateActiveDestinations(state, mutationSession) {
14
14
  const activeDests = getActiveDestinations(state, state.selected.square);
15
15
  return mutationSession.addMutation('state.interaction.updateActiveDestinations', interactionSetActiveDestinations(state, activeDests));
16
16
  }
17
+ export function isDragSessionCoreOwned(session) {
18
+ return session.owner === 'core';
19
+ }
@@ -1,11 +1,19 @@
1
1
  import type { ReadonlyDeep } from 'type-fest';
2
+ import type { ExtensionDragSession, ExtensionDragSessionBase } from '../../../extensions/types/basic/interaction.js';
2
3
  import type { MoveRequest, PieceCode, RolePromotionCode, Square } from '../../board/types/internal.js';
3
- export interface DragSession {
4
+ export type DragSessionExtensionOwned = ExtensionDragSession & {
5
+ owner: string;
6
+ };
7
+ export type DragSessionExtensionOwnedSnapshot = ReadonlyDeep<DragSessionExtensionOwned>;
8
+ export interface DragSessionCoreOwned extends ExtensionDragSessionBase {
9
+ owner: 'core';
4
10
  type: 'lifted-piece-drag' | 'release-targeting';
5
11
  sourceSquare: Square;
6
12
  sourcePieceCode: PieceCode;
7
13
  targetSquare: Square | null;
8
14
  }
15
+ export type DragSessionCoreOwnedSnapshot = ReadonlyDeep<DragSessionCoreOwned>;
16
+ export type DragSession = DragSessionCoreOwned | DragSessionExtensionOwned;
9
17
  export type DragSessionSnapshot = ReadonlyDeep<DragSession>;
10
18
  export interface MoveDestination extends Omit<MoveRequest, 'from' | 'promotedTo'> {
11
19
  promotedTo?: RolePromotionCode[];
@@ -1,6 +1,6 @@
1
1
  import { PositionInput } from './board/types/input.js';
2
2
  import { BoardState, BoardStateSnapshot } from './board/types/main.js';
3
- import type { ChangeState, ChangeStateSnapshot } from './change/types.js';
3
+ import type { ChangeState, ChangeStateSnapshot } from './change/types/main.js';
4
4
  import { InteractionState, InteractionStateInitOptions, InteractionStateSnapshot } from './interaction/types/main.js';
5
5
  import { ViewStateSnapshot } from './view/types/internal.js';
6
6
  import { ViewState, ViewStateInitOptions } from './view/types/main.js';