@mirasen/chessboard 0.1.1 → 1.0.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 (139) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +240 -45
  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/build/index.d.ts +21 -0
  15. package/dist/extensions/build/index.js +9 -0
  16. package/dist/extensions/factory/main.js +23 -2
  17. package/dist/extensions/factory/runtime.d.ts +2 -1
  18. package/dist/extensions/factory/runtime.js +48 -4
  19. package/dist/extensions/first-party/active-target/factory.js +9 -9
  20. package/dist/extensions/first-party/active-target/types.d.ts +2 -2
  21. package/dist/extensions/first-party/active-target/types.js +4 -1
  22. package/dist/extensions/first-party/auto-promote/factory.d.ts +2 -0
  23. package/dist/extensions/first-party/auto-promote/factory.js +51 -0
  24. package/dist/extensions/first-party/auto-promote/types.d.ts +12 -0
  25. package/dist/extensions/first-party/auto-promote/types.js +2 -0
  26. package/dist/extensions/first-party/board-events/factory.js +1 -16
  27. package/dist/extensions/first-party/board-events/types.d.ts +1 -4
  28. package/dist/extensions/first-party/common/helpers.d.ts +7 -7
  29. package/dist/extensions/first-party/common/helpers.js +11 -11
  30. package/dist/extensions/first-party/common/types.d.ts +2 -3
  31. package/dist/extensions/first-party/last-move/factory.js +10 -10
  32. package/dist/extensions/first-party/last-move/types.d.ts +2 -2
  33. package/dist/extensions/first-party/legal-moves/factory.js +5 -5
  34. package/dist/extensions/first-party/legal-moves/types.d.ts +2 -2
  35. package/dist/extensions/first-party/main-renderer/animation/render.js +1 -2
  36. package/dist/extensions/first-party/main-renderer/animation/update.js +9 -23
  37. package/dist/extensions/first-party/main-renderer/board/render.js +3 -3
  38. package/dist/extensions/first-party/main-renderer/board/update.js +5 -3
  39. package/dist/extensions/first-party/main-renderer/coordinates/render.js +4 -4
  40. package/dist/extensions/first-party/main-renderer/drag/render.js +1 -1
  41. package/dist/extensions/first-party/main-renderer/factory.js +4 -4
  42. package/dist/extensions/first-party/main-renderer/normalize.d.ts +2 -3
  43. package/dist/extensions/first-party/main-renderer/normalize.js +1 -1
  44. package/dist/extensions/first-party/main-renderer/pieces/render.js +5 -5
  45. package/dist/extensions/first-party/main-renderer/pieces/suppress.js +7 -2
  46. package/dist/extensions/first-party/main-renderer/types/extension.d.ts +1 -1
  47. package/dist/extensions/first-party/main-renderer/types/extension.js +1 -1
  48. package/dist/extensions/first-party/main-renderer/types/instance.d.ts +2 -2
  49. package/dist/extensions/first-party/promotion/factory.d.ts +3 -0
  50. package/dist/extensions/first-party/promotion/factory.js +219 -0
  51. package/dist/extensions/first-party/promotion/normalize.d.ts +3 -0
  52. package/dist/extensions/first-party/promotion/normalize.js +37 -0
  53. package/dist/extensions/first-party/promotion/types/input.d.ts +10 -0
  54. package/dist/extensions/first-party/promotion/types/internal.d.ts +12 -0
  55. package/dist/extensions/first-party/promotion/types/internal.js +22 -0
  56. package/dist/extensions/first-party/promotion/types/main.d.ts +24 -0
  57. package/dist/extensions/first-party/promotion/types/main.js +2 -0
  58. package/dist/extensions/first-party/selected-square/factory.js +7 -7
  59. package/dist/extensions/first-party/selected-square/types.d.ts +2 -2
  60. package/dist/extensions/index.d.ts +1 -8
  61. package/dist/extensions/index.js +1 -6
  62. package/dist/extensions/types/basic/events.d.ts +8 -31
  63. package/dist/extensions/types/basic/events.js +9 -5
  64. package/dist/extensions/types/basic/interaction.d.ts +21 -0
  65. package/dist/extensions/types/basic/transient-visuals.d.ts +2 -1
  66. package/dist/extensions/types/context/events.d.ts +5 -0
  67. package/dist/extensions/types/context/events.js +1 -0
  68. package/dist/extensions/types/context/ui-move.d.ts +3 -15
  69. package/dist/extensions/types/extension.d.ts +10 -8
  70. package/dist/extensions/types/main.d.ts +11 -6
  71. package/dist/extensions/types/surface/commands.d.ts +9 -0
  72. package/dist/extensions/types/surface/events.d.ts +2 -3
  73. package/dist/extensions/types/wrapper.d.ts +23 -22
  74. package/dist/extensions/types/wrapper.js +34 -6
  75. package/dist/extensions/update.d.ts +2 -2
  76. package/dist/extensions/update.js +1 -1
  77. package/dist/index.d.ts +1 -2
  78. package/dist/layout/factory.js +3 -3
  79. package/dist/layout/geometry/factory.d.ts +2 -11
  80. package/dist/layout/geometry/factory.js +16 -17
  81. package/dist/layout/geometry/helpers.d.ts +5 -0
  82. package/dist/layout/geometry/helpers.js +33 -0
  83. package/dist/layout/geometry/types.d.ts +13 -12
  84. package/dist/layout/helpers.d.ts +3 -4
  85. package/dist/layout/helpers.js +5 -5
  86. package/dist/layout/reducers.js +9 -8
  87. package/dist/layout/types.d.ts +5 -5
  88. package/dist/render/mount.js +2 -2
  89. package/dist/render/rendering/state.js +7 -7
  90. package/dist/runtime/factory/events.d.ts +3 -0
  91. package/dist/runtime/factory/events.js +18 -0
  92. package/dist/runtime/factory/helpers.js +12 -12
  93. package/dist/runtime/factory/input.js +17 -10
  94. package/dist/runtime/factory/main.js +44 -4
  95. package/dist/runtime/input/adapter/event.d.ts +3 -0
  96. package/dist/runtime/input/adapter/event.js +19 -0
  97. package/dist/runtime/input/adapter/factory.js +19 -38
  98. package/dist/runtime/input/adapter/helpers.d.ts +3 -5
  99. package/dist/runtime/input/adapter/helpers.js +60 -12
  100. package/dist/runtime/input/adapter/pointer.d.ts +6 -10
  101. package/dist/runtime/input/adapter/pointer.js +37 -70
  102. package/dist/runtime/input/adapter/types.d.ts +6 -3
  103. package/dist/runtime/input/adapter/types.js +6 -1
  104. package/dist/runtime/input/controller/factory.js +12 -11
  105. package/dist/runtime/input/controller/pointer.d.ts +5 -6
  106. package/dist/runtime/input/controller/pointer.js +36 -35
  107. package/dist/runtime/input/controller/transient-visuals.d.ts +2 -2
  108. package/dist/runtime/input/controller/transient-visuals.js +12 -5
  109. package/dist/runtime/input/controller/types.d.ts +5 -4
  110. package/dist/runtime/lifecycle.js +6 -6
  111. package/dist/runtime/mutation/interaction.js +1 -5
  112. package/dist/runtime/mutation/types.d.ts +10 -8
  113. package/dist/runtime/types/main.d.ts +2 -0
  114. package/dist/state/change/{factory.d.ts → factory/main.d.ts} +1 -1
  115. package/dist/state/change/{factory.js → factory/main.js} +8 -5
  116. package/dist/state/change/factory/ui-move.d.ts +4 -0
  117. package/dist/{runtime → state/change}/factory/ui-move.js +9 -9
  118. package/dist/state/change/helpers.d.ts +4 -2
  119. package/dist/state/change/helpers.js +11 -1
  120. package/dist/state/change/mutation.d.ts +1 -1
  121. package/dist/state/change/reducers.d.ts +3 -3
  122. package/dist/state/change/reducers.js +4 -4
  123. package/dist/state/change/types/main.d.ts +18 -0
  124. package/dist/state/change/types/main.js +1 -0
  125. package/dist/state/change/types/ui-move.d.ts +25 -0
  126. package/dist/state/change/types/ui-move.js +1 -0
  127. package/dist/state/factory.js +1 -1
  128. package/dist/state/interaction/factory.js +4 -2
  129. package/dist/state/interaction/helpers.d.ts +2 -0
  130. package/dist/state/interaction/helpers.js +3 -0
  131. package/dist/state/interaction/types/internal.d.ts +9 -1
  132. package/dist/state/types.d.ts +1 -1
  133. package/dist/wrapper/factory.js +2 -26
  134. package/package.json +17 -11
  135. package/dist/runtime/factory/ui-move.d.ts +0 -4
  136. package/dist/runtime/types/ui-move.d.ts +0 -9
  137. package/dist/state/change/types.d.ts +0 -17
  138. /package/dist/{runtime/types/ui-move.js → extensions/first-party/promotion/types/input.js} +0 -0
  139. /package/dist/{state/change/types.js → extensions/types/basic/interaction.js} +0 -0
@@ -1,32 +1,31 @@
1
1
  import { fileOf, rankOf } from '../../state/board/coords.js';
2
- /**
3
- * Create RenderGeometry for a given board size (in px) and orientation.
4
- * - Geometry uses an SVG-local coordinate system with origin at top-left.
5
- * - Orientation mapping:
6
- * - 'white': files increase left→right (a..h), ranks increase bottom→top (1..8).
7
- * yIndex = 7 - rank, xIndex = file
8
- * - 'black': files increase right→left (a..h), ranks increase top→bottom (1..8).
9
- * yIndex = rank, xIndex = 7 - file
10
- */
11
- export function createRenderGeometry(boardSize, orientation) {
12
- if (!(boardSize > 0 && Number.isFinite(boardSize))) {
13
- throw new RangeError(`Invalid boardSize: ${boardSize}`);
2
+ import { makeBoardRect, measureBoardSize } from './helpers.js';
3
+ export function createRenderGeometry(sceneSize, orientation) {
4
+ if (!(sceneSize.height > 0 &&
5
+ Number.isFinite(sceneSize.height) &&
6
+ sceneSize.width > 0 &&
7
+ Number.isFinite(sceneSize.width))) {
8
+ throw new RangeError(`Invalid sceneSize: ${JSON.stringify(sceneSize)}`);
14
9
  }
10
+ const boardSize = measureBoardSize(sceneSize);
11
+ const boardRect = makeBoardRect(sceneSize);
15
12
  const squareSize = boardSize / 8;
16
13
  const white = orientation === 0 /* ColorCode.White */;
17
14
  return {
18
- boardSize,
15
+ sceneSize: { ...sceneSize },
16
+ boardRect,
19
17
  squareSize,
20
18
  orientation,
21
- squareRect(sq) {
19
+ getSquareRect(sq) {
22
20
  const f = fileOf(sq);
23
21
  const r = rankOf(sq);
24
22
  const xIndex = white ? f : 7 - f;
25
23
  const yIndex = white ? 7 - r : r;
26
24
  return {
27
- x: xIndex * squareSize,
28
- y: yIndex * squareSize,
29
- size: squareSize
25
+ x: boardRect.x + xIndex * squareSize,
26
+ y: boardRect.y + yIndex * squareSize,
27
+ width: squareSize,
28
+ height: squareSize
30
29
  };
31
30
  }
32
31
  };
@@ -0,0 +1,5 @@
1
+ import { Rect, RectSnapshot, Size, SizeSnapshot } from './types.js';
2
+ export declare function sceneRectsEqual(rect1: RectSnapshot | null, rect2: RectSnapshot | null): boolean;
3
+ export declare function sceneSizesEqual(size1: SizeSnapshot | null, size2: SizeSnapshot | null): boolean;
4
+ export declare function measureBoardSize(sceneSize: Size): number;
5
+ export declare function makeBoardRect(sceneSize: Size): Rect;
@@ -0,0 +1,33 @@
1
+ export function sceneRectsEqual(rect1, rect2) {
2
+ if (rect1 === null && rect2 === null) {
3
+ return true;
4
+ }
5
+ if (rect1 === null || rect2 === null) {
6
+ return false;
7
+ }
8
+ return (rect1.x === rect2.x &&
9
+ rect1.y === rect2.y &&
10
+ rect1.width === rect2.width &&
11
+ rect1.height === rect2.height);
12
+ }
13
+ export function sceneSizesEqual(size1, size2) {
14
+ if (size1 === null && size2 === null) {
15
+ return true;
16
+ }
17
+ if (size1 === null || size2 === null) {
18
+ return false;
19
+ }
20
+ return size1.width === size2.width && size1.height === size2.height;
21
+ }
22
+ export function measureBoardSize(sceneSize) {
23
+ return Math.min(sceneSize.width, sceneSize.height);
24
+ }
25
+ export function makeBoardRect(sceneSize) {
26
+ const boardSize = measureBoardSize(sceneSize);
27
+ return {
28
+ x: (sceneSize.width - boardSize) / 2,
29
+ y: (sceneSize.height - boardSize) / 2,
30
+ width: boardSize,
31
+ height: boardSize
32
+ };
33
+ }
@@ -1,20 +1,21 @@
1
1
  import { ReadonlyDeep } from 'type-fest';
2
2
  import { ColorCode, Square } from '../../state/board/types/internal.js';
3
- export interface SquareRect {
3
+ export interface Size {
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export type SizeSnapshot = ReadonlyDeep<Size>;
8
+ export interface Rect {
4
9
  x: number;
5
10
  y: number;
6
- size: number;
11
+ width: number;
12
+ height: number;
7
13
  }
8
- export type SquareRectSnapshot = ReadonlyDeep<SquareRect>;
9
- /**
10
- * RenderGeometry represents the derived geometry of the rendered chessboard.
11
- * It includes the board size, square size, orientation, and square rectangle mapping.
12
- *
13
- * It is computed from layout inputs and used by rendering and interaction code.
14
- */
15
- export interface RenderGeometry {
16
- readonly boardSize: number;
14
+ export type RectSnapshot = ReadonlyDeep<Rect>;
15
+ export interface SceneRenderGeometry {
16
+ readonly sceneSize: SizeSnapshot;
17
+ readonly boardRect: RectSnapshot;
17
18
  readonly squareSize: number;
18
19
  readonly orientation: ColorCode;
19
- squareRect(sq: Square): SquareRectSnapshot;
20
+ getSquareRect(sq: Square): RectSnapshot;
20
21
  }
@@ -1,4 +1,3 @@
1
- /**
2
- * Measure board size from container (square board fits within host).
3
- */
4
- export declare function measureBoardSize(container: HTMLElement): number;
1
+ import { Size } from './geometry/types.js';
2
+ export declare function measureSceneSize(container: HTMLElement): Size;
3
+ export declare function isSceneSizeValid(size: Size | null): boolean;
@@ -1,6 +1,6 @@
1
- /**
2
- * Measure board size from container (square board fits within host).
3
- */
4
- export function measureBoardSize(container) {
5
- return Math.min(container.clientWidth, container.clientHeight);
1
+ export function measureSceneSize(container) {
2
+ return { width: container.clientWidth, height: container.clientHeight };
3
+ }
4
+ export function isSceneSizeValid(size) {
5
+ return size !== null && size.width > 0 && size.height > 0;
6
6
  }
@@ -1,5 +1,6 @@
1
1
  import { createRenderGeometry } from './geometry/factory.js';
2
- import { measureBoardSize } from './helpers.js';
2
+ import { sceneSizesEqual } from './geometry/helpers.js';
3
+ import { isSceneSizeValid, measureSceneSize } from './helpers.js';
3
4
  export function layoutRefreshGeometry(state, options) {
4
5
  const orientation = options.orientation ?? state.orientation;
5
6
  if (orientation === null) {
@@ -7,23 +8,23 @@ export function layoutRefreshGeometry(state, options) {
7
8
  // For now we throw
8
9
  throw new Error('Orientation is expected to be provided on the first pass of layout.refreshGeometry');
9
10
  }
10
- let boardSize = options.container ? measureBoardSize(options.container) : state.boardSize;
11
- if (boardSize !== null && boardSize <= 0)
12
- boardSize = null;
13
- const changed = boardSize !== state.boardSize || orientation !== state.orientation;
11
+ let sceneSize = options.container ? measureSceneSize(options.container) : state.sceneSize;
12
+ if (sceneSize !== null && !isSceneSizeValid(sceneSize))
13
+ sceneSize = null;
14
+ const changed = !sceneSizesEqual(sceneSize, state.sceneSize) || orientation !== state.orientation;
14
15
  if (!changed) {
15
16
  return false; // no-op, no relevant changes
16
17
  }
17
18
  // Update the state
18
- state.boardSize = boardSize;
19
+ state.sceneSize = sceneSize;
19
20
  state.orientation = orientation;
20
- if (boardSize === null || orientation === null) {
21
+ if (sceneSize === null || orientation === null) {
21
22
  // General invalid-geometry rule. Orientation is currently guarded above,
22
23
  // but this branch stays explicit in case null orientation is allowed later.
23
24
  state.geometry = null;
24
25
  }
25
26
  else {
26
- state.geometry = createRenderGeometry(boardSize, orientation);
27
+ state.geometry = createRenderGeometry(sceneSize, orientation);
27
28
  }
28
29
  state.layoutEpoch++;
29
30
  return true;
@@ -1,11 +1,11 @@
1
1
  import { ReadonlyDeep } from 'type-fest';
2
2
  import { ColorCode } from '../state/board/types/internal.js';
3
- import { RenderGeometry } from './geometry/types.js';
3
+ import { SceneRenderGeometry, Size } from './geometry/types.js';
4
4
  import { LayoutMutationSession } from './mutation.js';
5
5
  export interface LayoutInternal {
6
- boardSize: number | null;
6
+ sceneSize: Size | null;
7
7
  orientation: ColorCode | null;
8
- geometry: RenderGeometry | null;
8
+ geometry: SceneRenderGeometry | null;
9
9
  layoutEpoch: number;
10
10
  }
11
11
  export type LayoutSnapshot = ReadonlyDeep<LayoutInternal>;
@@ -14,9 +14,9 @@ export interface LayoutRefreshOptions {
14
14
  orientation?: ColorCode;
15
15
  }
16
16
  export interface Layout {
17
- readonly boardSize: number | null;
17
+ readonly sceneSize: Size | null;
18
18
  readonly orientation: ColorCode | null;
19
- readonly geometry: RenderGeometry | null;
19
+ readonly geometry: SceneRenderGeometry | null;
20
20
  readonly layoutEpoch: number;
21
21
  refreshGeometry(options: LayoutRefreshOptions, mutationSession: LayoutMutationSession): boolean;
22
22
  getSnapshot(): LayoutSnapshot;
@@ -8,13 +8,13 @@ export function renderMount(state, element) {
8
8
  // Now call onMount for all extensions with their slot roots
9
9
  for (const extensionRec of state.extensions.values()) {
10
10
  const slotRoots = extensionRec.render.slots;
11
- extensionRec.extension.instance.mount({ slotRoots });
11
+ extensionRec.extension.instance.mount?.({ slotRoots });
12
12
  }
13
13
  }
14
14
  export function renderUnmount(state) {
15
15
  validateIsMounted(state);
16
16
  for (const extensionRec of state.extensions.values()) {
17
- extensionRec.extension.instance.unmount();
17
+ extensionRec.extension.instance.unmount?.();
18
18
  // clear the slot roots
19
19
  for (const slotRoot of Object.values(extensionRec.render.slots)) {
20
20
  clearElementChildren(slotRoot);
@@ -1,3 +1,4 @@
1
+ import { sceneSizesEqual } from '../../layout/geometry/helpers.js';
1
2
  import { updateElementAttributes } from '../svg/helpers.js';
2
3
  import { validateIsMounted } from './helpers.js';
3
4
  export function checkNeedsRender(state) {
@@ -16,14 +17,13 @@ export function performRenderPass(state, request) {
16
17
  if (!request.layout.geometry) {
17
18
  throw new Error('render() called without a valid layout geometry');
18
19
  }
19
- const currentSize = request.layout.geometry.boardSize;
20
- const prevSize = state.currentFrame?.layout.geometry?.boardSize;
21
- if (currentSize !== prevSize) {
22
- const size = String(currentSize);
20
+ const currentSize = request.layout.geometry.sceneSize;
21
+ const prevSize = state.currentFrame?.layout.geometry?.sceneSize ?? null;
22
+ if (!sceneSizesEqual(currentSize, prevSize)) {
23
23
  updateElementAttributes(state.svgRoots.svgRoot, {
24
- width: size,
25
- height: size,
26
- viewBox: `0 0 ${size} ${size}`
24
+ width: currentSize.width.toString(),
25
+ height: currentSize.height.toString(),
26
+ viewBox: `0 0 ${currentSize.width} ${currentSize.height}`
27
27
  });
28
28
  }
29
29
  // Check if we have any invalidation states
@@ -0,0 +1,3 @@
1
+ import { ExtensionRuntimeSurfaceEvents } from '../../extensions/types/surface/events.js';
2
+ import { GetInternalState } from '../types/main.js';
3
+ export declare function createExtensionRuntimeSurfaceEvents(getInternalState: GetInternalState): ExtensionRuntimeSurfaceEvents;
@@ -0,0 +1,18 @@
1
+ import assert from '@ktarmyshov/assert';
2
+ import { NEED_EVENT_TYPES } from '../input/adapter/types.js';
3
+ export function createExtensionRuntimeSurfaceEvents(getInternalState) {
4
+ return {
5
+ subscribeEvent(type) {
6
+ const internalState = getInternalState();
7
+ assert(internalState.inputAdapter, 'Input adapter not available in the current runtime state');
8
+ internalState.inputAdapter.subscribeEvent(type);
9
+ },
10
+ unsubscribeEvent(type) {
11
+ const internalState = getInternalState();
12
+ assert(internalState.inputAdapter, 'Input adapter not available in the current runtime state');
13
+ if (!NEED_EVENT_TYPES.has(type)) {
14
+ internalState.inputAdapter.unsubscribeEvent(type);
15
+ }
16
+ }
17
+ };
18
+ }
@@ -1,9 +1,9 @@
1
1
  import assert from '@ktarmyshov/assert';
2
2
  import { isEmptyPieceCode } from '../../state/board/check.js';
3
+ import { createPendingUIMoveRequest } from '../../state/change/factory/ui-move.js';
3
4
  import { canMoveTo } from '../input/controller/helpers.js';
4
- import { createUIMoveRequestContext } from './ui-move.js';
5
- function assertResolvedAfterAutoResolve(state) {
6
- if (state.status !== 'resolved') {
5
+ function assertResolvedAfterAutoResolve(request) {
6
+ if (request.status !== 'resolved') {
7
7
  throw new Error('UI move request must be resolved after autoresolve');
8
8
  }
9
9
  }
@@ -22,18 +22,18 @@ export function uiMoveCompleteTo(state, target) {
22
22
  assert(canMoveTo(interaction.getSnapshot(), target), 'dropTo/releaseTo target must be a valid move destination');
23
23
  // canMoveTo already confirmed we can move, but if 'FREE' movability it can be that there are no activeDestinations for the target square, so we need to handle that case as well
24
24
  const destination = interaction.activeDestinations.get(target) ?? { to: target };
25
- const moveRequestContext = createUIMoveRequestContext(dragSession.sourceSquare, destination);
26
- state.extensionSystem.onUIMoveRequest(moveRequestContext);
27
- if (moveRequestContext.status === 'deferred') {
28
- state.state.change.setDeferredUIMoveRequestContext(moveRequestContext, mutationSession);
25
+ const pendingUIMoveRequest = createPendingUIMoveRequest(dragSession.sourceSquare, destination);
26
+ state.extensionSystem.onUIMoveRequest({ request: pendingUIMoveRequest });
27
+ if (pendingUIMoveRequest.status === 'deferred') {
28
+ state.state.change.setDeferredUIMoveRequest(pendingUIMoveRequest, mutationSession);
29
29
  }
30
- else if (moveRequestContext.status === 'unresolved') {
30
+ else if (pendingUIMoveRequest.status === 'unresolved') {
31
31
  // Try to auto-resolve if possible (e.g. if there are no promotion choices to be made)
32
- moveRequestContext.autoresolve();
33
- assertResolvedAfterAutoResolve(moveRequestContext);
32
+ pendingUIMoveRequest.autoresolve();
33
+ assertResolvedAfterAutoResolve(pendingUIMoveRequest);
34
34
  }
35
- if (moveRequestContext.status === 'resolved') {
36
- const moveRequest = moveRequestContext.resolvedMoveRequest;
35
+ if (pendingUIMoveRequest.status === 'resolved') {
36
+ const moveRequest = pendingUIMoveRequest.resolvedMoveRequest;
37
37
  if (moveRequest !== null) {
38
38
  state.state.board.move(moveRequest, mutationSession);
39
39
  }
@@ -1,5 +1,6 @@
1
1
  import assert from '@ktarmyshov/assert';
2
2
  import { isEmptyPieceCode } from '../../state/board/check.js';
3
+ import { isDragSessionCoreOwned } from '../../state/interaction/helpers.js';
3
4
  import { runtimeRunMutationPipeline } from '../mutation/run.js';
4
5
  import { uiMoveCompleteTo } from './helpers.js';
5
6
  export function createRuntimeInteractionSurface(state) {
@@ -24,6 +25,7 @@ export function createRuntimeInteractionSurface(state) {
24
25
  };
25
26
  interaction.setSelected(interactionSource, interactionMutationSession);
26
27
  const dragSession = {
28
+ owner: 'core',
27
29
  type: 'lifted-piece-drag',
28
30
  sourceSquare: interactionSource.square,
29
31
  sourcePieceCode: interactionSource.pieceCode,
@@ -36,27 +38,27 @@ export function createRuntimeInteractionSurface(state) {
36
38
  const internalState = state();
37
39
  internalState.renderSystem.requestRenderVisuals(input);
38
40
  },
39
- dropTo(target) {
41
+ completeCoreDragTo(target) {
40
42
  const internalState = state();
41
43
  const mutationSession = internalState.mutation.getSession();
42
44
  const interaction = internalState.state.interaction;
43
45
  const dragSession = interaction.dragSession;
44
- assert(dragSession !== null, 'dropTo requires an active drag session');
45
- assert(dragSession.type === 'lifted-piece-drag', 'dropTo requires a lifted-piece-drag session');
46
+ assert(dragSession !== null, 'completeDragTo requires an active drag session');
47
+ assert(isDragSessionCoreOwned(dragSession), 'completeDragTo can only be called for core-owned drag sessions');
46
48
  uiMoveCompleteTo(internalState, target);
47
- mutationSession.addMutation('runtime.interaction.dropTo', true);
49
+ mutationSession.addMutation('runtime.interaction.completeDragTo', true, dragSession);
48
50
  runtimeRunMutationPipeline(internalState);
49
51
  },
50
- releaseTo(target) {
52
+ completeExtensionDrag(target) {
51
53
  const internalState = state();
52
54
  const mutationSession = internalState.mutation.getSession();
53
55
  const interaction = internalState.state.interaction;
54
56
  const dragSession = interaction.dragSession;
55
- assert(dragSession !== null, 'releaseTo requires an active drag session');
56
- assert(dragSession.type === 'release-targeting', 'releaseTo requires a release-targeting session');
57
- uiMoveCompleteTo(internalState, target);
58
- mutationSession.addMutation('runtime.interaction.releaseTo', true);
59
- runtimeRunMutationPipeline(internalState);
57
+ assert(dragSession !== null, 'completeExtensionDrag requires an active drag session');
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
+ internalState.extensionSystem.completeDrag(dragSession);
61
+ internalState.state.interaction.setDragSession(null, mutationSession);
60
62
  },
61
63
  startReleaseTargetingDrag(source, target) {
62
64
  const internalState = state();
@@ -65,6 +67,7 @@ export function createRuntimeInteractionSurface(state) {
65
67
  assert(interaction.selected !== null, 'startReleaseTargetingDrag requires a selected piece');
66
68
  assert(interaction.selected.square === source, 'startReleaseTargetingDrag source must match selected square');
67
69
  const dragSession = {
70
+ owner: 'core',
68
71
  type: 'release-targeting',
69
72
  sourceSquare: source,
70
73
  sourcePieceCode: interaction.selected.pieceCode,
@@ -93,6 +96,10 @@ export function createRuntimeInteractionSurface(state) {
93
96
  assert(currentDragSession !== null, 'No active drag session to update');
94
97
  internalState.state.interaction.updateDragSessionCurrentTarget(target, interactionMutationSession);
95
98
  runtimeRunMutationPipeline(internalState);
99
+ },
100
+ onEvent(context) {
101
+ const internalState = state();
102
+ internalState.extensionSystem.onEvent(context);
96
103
  }
97
104
  };
98
105
  }
@@ -10,6 +10,7 @@ import { createInteractionController } from '../input/controller/factory.js';
10
10
  import { runtimeDestroy, runtimeMount, runtimeUnmount } from '../lifecycle.js';
11
11
  import { createRuntimeMutationPipeline } from '../mutation/factory.js';
12
12
  import { runtimeRunMutationPipeline } from '../mutation/run.js';
13
+ import { createExtensionRuntimeSurfaceEvents } from './events.js';
13
14
  import { createRuntimeInteractionSurface } from './input.js';
14
15
  function createRuntimeInternal(options) {
15
16
  const extensionSystem = createExtensionSystem(options);
@@ -30,7 +31,7 @@ function createRuntimeInternal(options) {
30
31
  })
31
32
  };
32
33
  }
33
- function createExtensionRuntimeSurfaceCommands(getInternalState) {
34
+ function createExtensionRuntimeSurfaceCommandsInternalSurface(getInternalState) {
34
35
  return {
35
36
  setPosition(input) {
36
37
  const state = getInternalState();
@@ -103,6 +104,13 @@ function createExtensionRuntimeSurfaceCommands(getInternalState) {
103
104
  runtimeRunMutationPipeline(state);
104
105
  return changed;
105
106
  },
107
+ startDrag(session) {
108
+ const state = getInternalState();
109
+ const mutationSession = state.mutation.getSession();
110
+ const changed = state.state.interaction.setDragSession(session, mutationSession);
111
+ runtimeRunMutationPipeline(state);
112
+ return changed;
113
+ },
106
114
  clearActiveInteraction() {
107
115
  const state = getInternalState();
108
116
  const mutationSession = state.mutation.getSession();
@@ -117,6 +125,36 @@ function createExtensionRuntimeSurfaceCommands(getInternalState) {
117
125
  runtimeRunMutationPipeline(state);
118
126
  return changed;
119
127
  },
128
+ resolveDeferredUIMoveRequest(details) {
129
+ const state = getInternalState();
130
+ const deferredRequest = state.state.change.deferredUIMoveRequest;
131
+ assert(deferredRequest, 'No deferred UI move request to resolve');
132
+ const { sourceSquare, destination } = deferredRequest;
133
+ const moveRequest = {
134
+ from: sourceSquare,
135
+ to: destination.to,
136
+ ...(destination.capturedSquare !== undefined && {
137
+ capturedSquare: destination.capturedSquare
138
+ }),
139
+ ...(destination.secondary !== undefined && { secondary: destination.secondary }),
140
+ promotedTo: details.promotedTo
141
+ };
142
+ const mutationSession = state.mutation.getSession();
143
+ const move = state.state.board.move(moveRequest, mutationSession);
144
+ state.state.change.setDeferredUIMoveRequest(null, mutationSession);
145
+ mutationSession.addMutation('runtime.interaction.resolveDeferredUIMoveRequest', true);
146
+ runtimeRunMutationPipeline(state);
147
+ return move;
148
+ },
149
+ cancelDeferredUIMoveRequest() {
150
+ const state = getInternalState();
151
+ assert(state.state.change.deferredUIMoveRequest, 'No deferred UI move request to cancel');
152
+ const mutationSession = state.mutation.getSession();
153
+ const changed = state.state.change.setDeferredUIMoveRequest(null, mutationSession);
154
+ mutationSession.addMutation('runtime.interaction.cancelDeferredUIMoveRequest', changed);
155
+ runtimeRunMutationPipeline(state);
156
+ return changed;
157
+ },
120
158
  getSnapshot() {
121
159
  const state = getInternalState();
122
160
  return {
@@ -139,11 +177,13 @@ export function createRuntime(options) {
139
177
  return internalState;
140
178
  }
141
179
  // Create RuntimeExtensionSurface to pass to the extension system for initialization of extension instances
142
- const extensionSurface = createExtensionRuntimeSurfaceCommands(getInternalState);
180
+ const extensionSurfaceCommands = createExtensionRuntimeSurfaceCommandsInternalSurface(getInternalState);
181
+ const extensionSurfaceEvents = createExtensionRuntimeSurfaceEvents(getInternalState);
143
182
  // Now construct the internal state
144
183
  const optionsInternal = {
145
184
  ...options,
146
- extensionRuntimeSurfaceCommands: extensionSurface,
185
+ extensionRuntimeSurfaceCommands: extensionSurfaceCommands,
186
+ extensionRuntimeSurfaceEvents: extensionSurfaceEvents,
147
187
  getInternalState
148
188
  };
149
189
  internalState = createRuntimeInternal(optionsInternal);
@@ -174,7 +214,7 @@ export function createRuntime(options) {
174
214
  internalStatus = 'destroyed';
175
215
  internalState = null;
176
216
  },
177
- ...extensionSurface,
217
+ ...extensionSurfaceCommands,
178
218
  getExtensionsPublicRecord() {
179
219
  return getInternalState().extensionSystem.getPublicRecord();
180
220
  }
@@ -0,0 +1,3 @@
1
+ import { InputAdapterInternal } from './types.js';
2
+ export declare function eventHandler(state: InputAdapterInternal, e: Event): void;
3
+ export declare function eventDestroy(state: InputAdapterInternal): void;
@@ -0,0 +1,19 @@
1
+ import { ALL_SCENE_POINTER_EVENT_TYPES } from '../../../extensions/types/basic/events.js';
2
+ import { pointerEventDestroy, pointerEventHandler } from './pointer.js';
3
+ export function eventHandler(state, e) {
4
+ let context = null;
5
+ if (ALL_SCENE_POINTER_EVENT_TYPES.has(e.type)) {
6
+ context = pointerEventHandler(state, e);
7
+ }
8
+ if (!context) {
9
+ context = {
10
+ rawEvent: e,
11
+ sceneEvent: null
12
+ };
13
+ }
14
+ // Pass to controller for extension handling
15
+ state.controller.onEvent(context);
16
+ }
17
+ export function eventDestroy(state) {
18
+ pointerEventDestroy(state);
19
+ }
@@ -1,55 +1,36 @@
1
- import { isPointerRelevantEvent, onPointerCancel, onPointerDown, onPointerLeave, onPointerMove, onPointerUp, releaseCapture } from './pointer.js';
1
+ import assert from '@ktarmyshov/assert';
2
+ import { eventDestroy, eventHandler } from './event.js';
3
+ import { NEED_EVENT_TYPES } from './types.js';
2
4
  function createInputAdapterInternal(options) {
3
5
  return {
4
6
  container: options.container,
5
- getGeometry: options.getGeometry,
7
+ getRenderGeometry: options.getRenderGeometry,
6
8
  controller: options.controller,
7
9
  activePointerId: null
8
10
  };
9
11
  }
10
12
  export function createInputAdapter(options) {
11
13
  const internalState = createInputAdapterInternal(options);
12
- const onPointerDownHandler = (e) => {
13
- if (!isPointerRelevantEvent(e))
14
- return;
15
- onPointerDown(internalState, e);
16
- };
17
- const onPointerMoveHandler = (e) => {
18
- if (!isPointerRelevantEvent(e))
19
- return;
20
- onPointerMove(internalState, e);
21
- };
22
- const onPointerUpHandler = (e) => {
23
- if (!isPointerRelevantEvent(e))
24
- return;
25
- onPointerUp(internalState, e);
26
- };
27
- const onPointerCancelHandler = (e) => {
28
- if (!isPointerRelevantEvent(e))
29
- return;
30
- onPointerCancel(internalState, e);
31
- };
32
- const onPointerLeaveHandler = (e) => {
33
- if (!isPointerRelevantEvent(e))
34
- return;
35
- onPointerLeave(internalState, e);
14
+ const onEventHander = (e) => {
15
+ eventHandler(internalState, e);
36
16
  };
37
17
  const inputAdapter = {
18
+ subscribeEvent(type) {
19
+ internalState.container.addEventListener(type, onEventHander);
20
+ },
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.`);
23
+ internalState.container.removeEventListener(type, onEventHander);
24
+ },
38
25
  destroy() {
39
- if (internalState.activePointerId !== null) {
40
- releaseCapture(internalState);
26
+ for (const type of NEED_EVENT_TYPES) {
27
+ internalState.container.removeEventListener(type, onEventHander);
41
28
  }
42
- internalState.container.removeEventListener('pointerdown', onPointerDownHandler);
43
- internalState.container.removeEventListener('pointermove', onPointerMoveHandler);
44
- internalState.container.removeEventListener('pointerup', onPointerUpHandler);
45
- internalState.container.removeEventListener('pointercancel', onPointerCancelHandler);
46
- internalState.container.removeEventListener('pointerleave', onPointerLeaveHandler);
29
+ eventDestroy(internalState);
47
30
  }
48
31
  };
49
- internalState.container.addEventListener('pointerdown', onPointerDownHandler);
50
- internalState.container.addEventListener('pointermove', onPointerMoveHandler);
51
- internalState.container.addEventListener('pointerup', onPointerUpHandler);
52
- internalState.container.addEventListener('pointercancel', onPointerCancelHandler);
53
- internalState.container.addEventListener('pointerleave', onPointerLeaveHandler);
32
+ for (const type of NEED_EVENT_TYPES) {
33
+ internalState.container.addEventListener(type, onEventHander);
34
+ }
54
35
  return inputAdapter;
55
36
  }
@@ -1,5 +1,3 @@
1
- import { ScenePoint } from '../../../extensions/types/basic/transient-visuals.js';
2
- import { RenderGeometry } from '../../../layout/geometry/types.js';
3
- import { Square } from '../../../state/board/types/internal.js';
4
- export declare function mapBoardPointToSquare(x: number, y: number, geometry: RenderGeometry): Square | null;
5
- export declare function clampBoardPoint(point: ScenePoint, geometry: RenderGeometry): ScenePoint;
1
+ import { ScenePointerEvent } from '../../../extensions/types/basic/events.js';
2
+ import { InputAdapterInternal } from './types.js';
3
+ export declare function makeScenePointerEvent(state: InputAdapterInternal, e: PointerEvent): ScenePointerEvent;