@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,6 +1,6 @@
1
1
  import { createSvgElement, updateElementAttributes } from '../../render/svg/helpers.js';
2
2
  export function prepareMoveTrack(track, geometry, pieceUrls, layer) {
3
- const r = geometry.squareRect(track.fromSq);
3
+ const r = geometry.getSquareRect(track.fromSq);
4
4
  const url = pieceUrls[track.pieceCode];
5
5
  const root = createSvgElement(layer, 'image', {
6
6
  'data-chessboard-id': `animation-move-${track.id}`,
@@ -8,14 +8,14 @@ export function prepareMoveTrack(track, geometry, pieceUrls, layer) {
8
8
  href: url,
9
9
  x: r.x.toString(),
10
10
  y: r.y.toString(),
11
- width: r.size.toString(),
12
- height: r.size.toString()
11
+ width: r.width.toString(),
12
+ height: r.height.toString()
13
13
  });
14
14
  return { ...track, root };
15
15
  }
16
16
  export function renderMoveTrack(node, geometry, progress) {
17
- const from = geometry.squareRect(node.fromSq);
18
- const to = geometry.squareRect(node.toSq);
17
+ const from = geometry.getSquareRect(node.fromSq);
18
+ const to = geometry.getSquareRect(node.toSq);
19
19
  const x = from.x + (to.x - from.x) * progress;
20
20
  const y = from.y + (to.y - from.y) * progress;
21
21
  updateElementAttributes(node.root, {
@@ -1,13 +1,13 @@
1
1
  import type { PieceUrls } from '../../extensions/first-party/main-renderer/types/internal.js';
2
- import type { RenderGeometry } from '../../layout/geometry/types.js';
2
+ import type { SceneRenderGeometry } from '../../layout/geometry/types.js';
3
3
  import type { AnimationPlan } from '../types.js';
4
4
  import type { PreparedNodeMap } from './types.js';
5
5
  /**
6
6
  * Prepare all tracks in the plan — creates SVG nodes in `layer` for each track.
7
7
  * Returns a map keyed by track id for use in render/clean passes.
8
8
  */
9
- export declare function prepareAnimationPlan(plan: AnimationPlan, geometry: RenderGeometry, pieceUrls: PieceUrls, layer: SVGElement): PreparedNodeMap;
10
- export declare function renderAnimationPlan(nodes: PreparedNodeMap, geometry: RenderGeometry, progress: number): void;
9
+ export declare function prepareAnimationPlan(plan: AnimationPlan, geometry: SceneRenderGeometry, pieceUrls: PieceUrls, layer: SVGElement): PreparedNodeMap;
10
+ export declare function renderAnimationPlan(nodes: PreparedNodeMap, geometry: SceneRenderGeometry, progress: number): void;
11
11
  /**
12
12
  * Clean up all track nodes — removes SVG elements from the DOM.
13
13
  */
@@ -1,7 +1,7 @@
1
1
  import type { PieceUrls } from '../../extensions/first-party/main-renderer/types/internal.js';
2
- import type { RenderGeometry } from '../../layout/geometry/types.js';
2
+ import type { SceneRenderGeometry } from '../../layout/geometry/types.js';
3
3
  import type { AnimationTrackStatic } from '../types.js';
4
4
  import type { PreparedStaticNode } from './types.js';
5
- export declare function prepareStaticTrack(track: AnimationTrackStatic, geometry: RenderGeometry, pieceUrls: PieceUrls, layer: SVGElement): PreparedStaticNode;
5
+ export declare function prepareStaticTrack(track: AnimationTrackStatic, geometry: SceneRenderGeometry, pieceUrls: PieceUrls, layer: SVGElement): PreparedStaticNode;
6
6
  export declare function renderStaticTrack(_node: PreparedStaticNode): void;
7
7
  export declare function cleanStaticTrack(node: PreparedStaticNode): void;
@@ -1,6 +1,6 @@
1
1
  import { createSvgElement } from '../../render/svg/helpers.js';
2
2
  export function prepareStaticTrack(track, geometry, pieceUrls, layer) {
3
- const r = geometry.squareRect(track.sq);
3
+ const r = geometry.getSquareRect(track.sq);
4
4
  const url = pieceUrls[track.pieceCode];
5
5
  const root = createSvgElement(layer, 'image', {
6
6
  'data-chessboard-id': `animation-static-${track.id}`,
@@ -8,8 +8,8 @@ export function prepareStaticTrack(track, geometry, pieceUrls, layer) {
8
8
  href: url,
9
9
  x: r.x.toString(),
10
10
  y: r.y.toString(),
11
- width: r.size.toString(),
12
- height: r.size.toString()
11
+ width: r.width.toString(),
12
+ height: r.height.toString()
13
13
  });
14
14
  return { ...track, root };
15
15
  }
@@ -1,5 +1,8 @@
1
1
  import { ReadonlyDeep } from 'type-fest';
2
2
  import type { NonEmptyPieceCode, Square } from '../state/board/types/internal.js';
3
+ import { BoardStateSnapshot } from '../state/board/types/main.js';
4
+ import { ChangeStateSnapshot } from '../state/change/types/main.js';
5
+ import { InteractionStateSnapshot } from '../state/interaction/types/main.js';
3
6
  export interface AnimationTrackMove {
4
7
  id: number;
5
8
  pieceCode: NonEmptyPieceCode;
@@ -20,27 +23,23 @@ export interface AnimationTrackStatic {
20
23
  effect: 'static';
21
24
  }
22
25
  export type AnimationTrack = AnimationTrackMove | AnimationTrackFade | AnimationTrackStatic;
23
- export interface AnimationPlan {
24
- sessionId: number;
25
- tracks: AnimationTrack[];
26
- }
27
26
  export interface AnimationSession {
28
27
  id: number;
29
- tracks: AnimationTrack[];
28
+ plan: AnimationPlan;
30
29
  startTime: number;
31
30
  duration: number;
32
31
  }
33
32
  export type AnimationSessionSnapshot = ReadonlyDeep<AnimationSession>;
34
- export interface AnimationTrackMoveExclude {
35
- fromSq: Square;
36
- toSq: Square;
33
+ export interface AnimationPlanningSnapshot {
34
+ readonly board: BoardStateSnapshot;
35
+ readonly change: ChangeStateSnapshot;
36
+ readonly interaction: InteractionStateSnapshot;
37
37
  }
38
- export interface AnimationTrackSquareExclude {
39
- sq: Square;
38
+ export interface AnimationPlanningInput {
39
+ previous: AnimationPlanningSnapshot;
40
+ current: AnimationPlanningSnapshot;
40
41
  }
41
- export type AnimationTrackExclude = AnimationTrackMoveExclude | AnimationTrackSquareExclude;
42
- export declare function isMoveExclude(e: AnimationTrackExclude): e is AnimationTrackMoveExclude;
43
- export declare function isSquareExclude(e: AnimationTrackExclude): e is AnimationTrackSquareExclude;
44
- export interface CalculateAnimationTracksOptions {
45
- exclude?: AnimationTrackExclude[];
42
+ export interface AnimationPlan {
43
+ readonly tracks: readonly AnimationTrack[];
44
+ readonly suppressedSquares: ReadonlySet<Square>;
46
45
  }
@@ -1,6 +1 @@
1
- export function isMoveExclude(e) {
2
- return 'fromSq' in e && 'toSq' in e;
3
- }
4
- export function isSquareExclude(e) {
5
- return 'sq' in e && !('fromSq' in e) && !('toSq' in e);
6
- }
1
+ export {};
@@ -1,3 +1,4 @@
1
+ import assert from '@ktarmyshov/assert';
1
2
  import { createExtensionAnimationController } from '../animation/factory.js';
2
3
  import { createExtensionInvalidationState } from '../invalidation/factory.js';
3
4
  import { extensionSystemUpdateState, extensionSystemUpdateUIMoveRequest } from '../update.js';
@@ -6,11 +7,14 @@ function createExtensionSystemInternal(getInternalState, options) {
6
7
  const extensions = new Map();
7
8
  const extensionsArray = options.extensions ?? [];
8
9
  for (const extensionDef of extensionsArray) {
10
+ if (extensionDef.id === 'core') {
11
+ throw new Error('Extension id "core" is reserved and cannot be used by extensions');
12
+ }
9
13
  if (extensions.has(extensionDef.id)) {
10
14
  throw new Error(`Duplicate extension id found: ${extensionDef.id}`);
11
15
  }
12
16
  const instance = extensionDef.createInstance({
13
- runtimeSurface: createExtensionRuntimeSurface(getInternalState, options.extensionRuntimeSurfaceCommands, extensionDef)
17
+ runtimeSurface: createExtensionRuntimeSurface(getInternalState, options.extensionRuntimeSurfaceCommands, options.extensionRuntimeSurfaceEvents, extensionDef)
14
18
  });
15
19
  const record = {
16
20
  id: extensionDef.id,
@@ -72,6 +76,23 @@ export function createExtensionSystem(options) {
72
76
  onUIMoveRequest(context) {
73
77
  extensionSystemUpdateUIMoveRequest(internalState, context);
74
78
  },
79
+ onEvent(context) {
80
+ const subscribers = internalState.eventSubscribers.get(context.rawEvent.type);
81
+ if (subscribers) {
82
+ for (const extensionId of subscribers) {
83
+ const extensionRec = internalState.extensions.get(extensionId);
84
+ if (extensionRec) {
85
+ extensionRec.instance.onEvent?.(context);
86
+ }
87
+ }
88
+ }
89
+ },
90
+ completeDrag(session) {
91
+ const extensionRec = internalState.extensions.get(session.owner);
92
+ assert(extensionRec, `Extension record not found for completing drag session with owner ${session.owner}`);
93
+ assert(extensionRec.instance.completeDrag, `Extension instance does not have a completeDrag handler for completing drag session with owner ${session.owner}`);
94
+ extensionRec.instance.completeDrag(session);
95
+ },
75
96
  onUnmount() {
76
97
  internalState.transientVisualsSubscribers.clear();
77
98
  internalState.eventSubscribers.clear();
@@ -84,7 +105,7 @@ export function createExtensionSystem(options) {
84
105
  onDestroy() {
85
106
  // We assume that the onUnmout was already called by runtime, but let's still validate
86
107
  for (const extensionRec of internalState.extensions.values()) {
87
- extensionRec.instance.destroy();
108
+ extensionRec.instance.destroy?.();
88
109
  const onUnmountCalled = [
89
110
  extensionRec.invalidation.dirtyLayers === 0,
90
111
  extensionRec.animation.getAll().length === 0
@@ -1,5 +1,6 @@
1
1
  import type { AnyExtensionDefinition } from '../types/extension.js';
2
2
  import type { GetInternalState } from '../types/main.js';
3
3
  import type { ExtensionRuntimeSurfaceCommands } from '../types/surface/commands.js';
4
+ import { ExtensionRuntimeSurfaceEvents } from '../types/surface/events.js';
4
5
  import type { ExtensionRuntimeSurface } from '../types/surface/main.js';
5
- export declare function createExtensionRuntimeSurface(getInternalState: GetInternalState, commands: ExtensionRuntimeSurfaceCommands, extensionDef: AnyExtensionDefinition): ExtensionRuntimeSurface;
6
+ export declare function createExtensionRuntimeSurface(getInternalState: GetInternalState, commands: ExtensionRuntimeSurfaceCommands, events: ExtensionRuntimeSurfaceEvents, extensionDef: AnyExtensionDefinition): ExtensionRuntimeSurface;
@@ -33,11 +33,55 @@ function createExtensionRuntimeSurfaceAnimation(getInternalState, extensionDef)
33
33
  }
34
34
  };
35
35
  }
36
- export function createExtensionRuntimeSurface(getInternalState, commands, extensionDef) {
37
- // @ts-expect-error We will implement events later, for now we just return empty object for it to satisfy the interface
36
+ function createExtensionRuntimeSurfaceEvents(getInternalState, runtimeSurfaceEvents, extensionDef) {
38
37
  return {
39
- commands,
38
+ subscribeEvent(type) {
39
+ const internalState = getInternalState();
40
+ const extensionRec = internalState.extensions.get(extensionDef.id);
41
+ assert(extensionRec, 'Extension record not found for subscribing to events');
42
+ assert(extensionRec.instance.onEvent, 'Extension instance does not have an onEvent handler for subscribing to events');
43
+ let subscribers = internalState.eventSubscribers.get(type);
44
+ if (!subscribers) {
45
+ subscribers = new Set();
46
+ internalState.eventSubscribers.set(type, subscribers);
47
+ }
48
+ subscribers.add(extensionDef.id);
49
+ runtimeSurfaceEvents.subscribeEvent(type);
50
+ },
51
+ unsubscribeEvent(type) {
52
+ const internalState = getInternalState();
53
+ const subscribers = internalState.eventSubscribers.get(type);
54
+ if (subscribers) {
55
+ subscribers.delete(extensionDef.id);
56
+ if (subscribers.size === 0) {
57
+ internalState.eventSubscribers.delete(type);
58
+ runtimeSurfaceEvents.unsubscribeEvent(type);
59
+ }
60
+ }
61
+ }
62
+ };
63
+ }
64
+ function createExtensionRuntimeSurfaceCommands(getInternalState, runtimeSurfaceCommands, extensionDef) {
65
+ return {
66
+ ...runtimeSurfaceCommands,
67
+ startDrag(session) {
68
+ const internalState = getInternalState();
69
+ const extensionRec = internalState.extensions.get(extensionDef.id);
70
+ assert(extensionRec, 'Extension record not found for starting drag session');
71
+ assert(extensionRec.instance.completeDrag, 'Extension instance does not have a completeDrag handler for starting drag session');
72
+ const extSession = {
73
+ owner: extensionDef.id,
74
+ ...session
75
+ };
76
+ return runtimeSurfaceCommands.startDrag(extSession);
77
+ }
78
+ };
79
+ }
80
+ export function createExtensionRuntimeSurface(getInternalState, commands, events, extensionDef) {
81
+ return {
82
+ commands: createExtensionRuntimeSurfaceCommands(getInternalState, commands, extensionDef),
40
83
  animation: createExtensionRuntimeSurfaceAnimation(getInternalState, extensionDef),
41
- transientVisuals: createExtensionRuntimeSurfaceTransientVisuals(getInternalState, extensionDef)
84
+ transientVisuals: createExtensionRuntimeSurfaceTransientVisuals(getInternalState, extensionDef),
85
+ events: createExtensionRuntimeSurfaceEvents(getInternalState, events, extensionDef)
42
86
  };
43
87
  }
@@ -60,19 +60,19 @@ function createActiveTargetInstance(config) {
60
60
  return;
61
61
  }
62
62
  const geometry = context.currentFrame.layout.geometry;
63
- const r = geometry.squareRect(square);
63
+ const r = geometry.getSquareRect(square);
64
64
  const rectAttributes = {
65
65
  x: r.x.toString(),
66
66
  y: r.y.toString(),
67
- width: r.size.toString(),
68
- height: r.size.toString(),
67
+ width: r.width.toString(),
68
+ height: r.height.toString(),
69
69
  fill: internalState.config.squareColor.color,
70
70
  'fill-opacity': internalState.config.squareColor.opacity.toString(),
71
71
  'shape-rendering': 'crispEdges'
72
72
  };
73
73
  const radius = geometry.squareSize * internalState.config.halo.radiusRatio;
74
- const centerX = r.x + r.size / 2;
75
- const centerY = r.y + r.size / 2;
74
+ const centerX = r.x + r.width / 2;
75
+ const centerY = r.y + r.height / 2;
76
76
  const haloAttributes = {
77
77
  cx: centerX.toString(),
78
78
  cy: centerY.toString(),
@@ -0,0 +1,2 @@
1
+ import { AutoPromoteDefinition } from './types.js';
2
+ export declare function createAutoPromote(): AutoPromoteDefinition;
@@ -0,0 +1,51 @@
1
+ import { extensionCreateInternalBase } from '../common/helpers.js';
2
+ import { EXTENSION_ID, EXTENSION_SLOTS } from './types.js';
3
+ export function createAutoPromote() {
4
+ return {
5
+ id: EXTENSION_ID,
6
+ slots: EXTENSION_SLOTS,
7
+ createInstance() {
8
+ return createAutoPromoteInstance();
9
+ }
10
+ };
11
+ }
12
+ function createAutoPromoteInternal() {
13
+ return {
14
+ ...extensionCreateInternalBase(),
15
+ toQueen: false
16
+ };
17
+ }
18
+ function createAutoPromoteInstancePublic(state) {
19
+ return {
20
+ get toQueen() {
21
+ return state.toQueen;
22
+ },
23
+ set toQueen(value) {
24
+ state.toQueen = value;
25
+ }
26
+ };
27
+ }
28
+ function createAutoPromoteInstance() {
29
+ const internalState = createAutoPromoteInternal();
30
+ const publicInterface = createAutoPromoteInstancePublic(internalState);
31
+ return {
32
+ id: EXTENSION_ID,
33
+ onUIMoveRequest(context) {
34
+ if (!internalState.toQueen)
35
+ return;
36
+ // Check if we can promote to queen
37
+ const pendingRequest = context.request;
38
+ const activeDestination = pendingRequest.destination;
39
+ if (activeDestination.promotedTo?.includes(5 /* RoleCode.Queen */)) {
40
+ pendingRequest.resolve({
41
+ from: pendingRequest.sourceSquare,
42
+ to: activeDestination.to,
43
+ promotedTo: 5 /* RoleCode.Queen */
44
+ });
45
+ }
46
+ },
47
+ getPublic() {
48
+ return publicInterface;
49
+ }
50
+ };
51
+ }
@@ -0,0 +1,14 @@
1
+ import { ExtensionSlotSvgRoots } from '../../types/basic/mount.js';
2
+ import { ExtensionDefinition, ExtensionInstance } from '../../types/extension.js';
3
+ export declare const EXTENSION_SLOTS: readonly [];
4
+ export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
5
+ export declare const EXTENSION_ID: "autoPromote";
6
+ export type AutoPromoteDefinition = ExtensionDefinition<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, AutoPromotePublic>;
7
+ export interface AutoPromotePublic {
8
+ toQueen: boolean;
9
+ }
10
+ export type AutoPromoteInstance = ExtensionInstance<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, AutoPromotePublic>;
11
+ export type AutoPromoteSlotRoots = ExtensionSlotSvgRoots<typeof EXTENSION_SLOTS>;
12
+ export interface AutoPromoteInstanceInternal {
13
+ toQueen: boolean;
14
+ }
@@ -0,0 +1,2 @@
1
+ export const EXTENSION_SLOTS = [];
2
+ export const EXTENSION_ID = 'autoPromote';
@@ -1,5 +1,5 @@
1
1
  import { denormalizeMove } from '../../../state/board/denormalize.js';
2
- import { extensionCreateInternalBase, extensionDestroy, extensionMount, extensionUnmount } from '../common/helpers.js';
2
+ import { extensionCreateInternalBase } from '../common/helpers.js';
3
3
  import { EXTENSION_ID, EXTENSION_SLOTS } from './types.js';
4
4
  export function createBoardEvents() {
5
5
  return {
@@ -17,10 +17,6 @@ function createBoardEventsInternal() {
17
17
  onRawUpdate: null
18
18
  };
19
19
  }
20
- function extensionClean(state) {
21
- state.onUIMove = null;
22
- state.onRawUpdate = null;
23
- }
24
20
  function createBoardEventsInstancePublic(state) {
25
21
  return {
26
22
  setOnUIMove(callback) {
@@ -36,9 +32,6 @@ function createBoardEventsInstance() {
36
32
  const publicInterface = createBoardEventsInstancePublic(internalState);
37
33
  return {
38
34
  id: EXTENSION_ID,
39
- mount(env) {
40
- extensionMount(internalState, env.slotRoots);
41
- },
42
35
  onUpdate(context) {
43
36
  if (internalState.onRawUpdate) {
44
37
  internalState.onRawUpdate({
@@ -54,14 +47,6 @@ function createBoardEventsInstance() {
54
47
  },
55
48
  getPublic() {
56
49
  return publicInterface;
57
- },
58
- unmount() {
59
- extensionUnmount(internalState);
60
- extensionClean(internalState);
61
- },
62
- destroy() {
63
- extensionDestroy(internalState);
64
- extensionClean(internalState);
65
50
  }
66
51
  };
67
52
  }
@@ -3,7 +3,6 @@ import { MoveOutput } from '../../../state/board/types/output.js';
3
3
  import { ExtensionSlotSvgRoots } from '../../types/basic/mount.js';
4
4
  import { UpdateFrameSnapshot } from '../../types/basic/update.js';
5
5
  import { ExtensionDefinition, ExtensionInstance } from '../../types/extension.js';
6
- import { ExtensionInternal } from '../common/types.js';
7
6
  export declare const EXTENSION_SLOTS: readonly [];
8
7
  export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
9
8
  export declare const EXTENSION_ID: "events";
@@ -21,7 +20,7 @@ export interface BoardEventsPublic {
21
20
  }
22
21
  export type BoardEventsInstance = ExtensionInstance<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, BoardEventsPublic>;
23
22
  export type BoardEventsSlotRoots = ExtensionSlotSvgRoots<typeof EXTENSION_SLOTS>;
24
- export interface BoardEventsInstanceInternal extends ExtensionInternal<ExtensionSlotsType> {
23
+ export interface BoardEventsInstanceInternal {
25
24
  onUIMove: OnUIMoveCallback | null;
26
25
  onRawUpdate: OnRawUpdateCallback | null;
27
26
  }
@@ -57,13 +57,13 @@ function createLastMoveInstance(config) {
57
57
  return;
58
58
  }
59
59
  const geometry = context.currentFrame.layout.geometry;
60
- const rFrom = geometry.squareRect(fromSq);
61
- const rTo = geometry.squareRect(toSq);
60
+ const rFrom = geometry.getSquareRect(fromSq);
61
+ const rTo = geometry.getSquareRect(toSq);
62
62
  const rectFromAttributes = {
63
63
  x: rFrom.x.toString(),
64
64
  y: rFrom.y.toString(),
65
- width: rFrom.size.toString(),
66
- height: rFrom.size.toString(),
65
+ width: rFrom.width.toString(),
66
+ height: rFrom.height.toString(),
67
67
  fill: internalState.config.color,
68
68
  'fill-opacity': internalState.config.opacity.toString(),
69
69
  'shape-rendering': 'crispEdges'
@@ -71,8 +71,8 @@ function createLastMoveInstance(config) {
71
71
  const rectToAttributes = {
72
72
  x: rTo.x.toString(),
73
73
  y: rTo.y.toString(),
74
- width: rTo.size.toString(),
75
- height: rTo.size.toString(),
74
+ width: rTo.width.toString(),
75
+ height: rTo.height.toString(),
76
76
  fill: internalState.config.color,
77
77
  'fill-opacity': internalState.config.opacity.toString(),
78
78
  'shape-rendering': 'crispEdges'
@@ -70,7 +70,7 @@ function createLegalMovesInstance(config) {
70
70
  fill: 'none'
71
71
  }; // TODO: Remove, this is just for visual test
72
72
  for (const [square, destination] of interaction.activeDestinations) {
73
- const rect = geometry.squareRect(square);
73
+ const rect = geometry.getSquareRect(square);
74
74
  const circleX = rect.x + geometry.squareSize / 2;
75
75
  const circleY = rect.y + geometry.squareSize / 2;
76
76
  const selectedPieceCode = context.currentFrame.state.board.pieces[interaction.selected.square];
@@ -1,4 +1,3 @@
1
- import { collectSuppressedSquares } from '../../../../animation/planner.js';
2
1
  import { cleanAnimationPlan, prepareAnimationPlan, renderAnimationPlan } from '../../../../animation/render/plan.js';
3
2
  const EMPTY_SQUARES = new Set();
4
3
  export function rendererAnimationPrepare(state, context, layer) {
@@ -38,7 +37,7 @@ export function getAnimationSuppressedSquares(state) {
38
37
  const entry = state.entries.get(session.id);
39
38
  if (!entry)
40
39
  continue;
41
- for (const sq of collectSuppressedSquares(entry.plan.tracks)) {
40
+ for (const sq of entry.plan.suppressedSquares) {
42
41
  result.add(sq);
43
42
  }
44
43
  }
@@ -1,40 +1,26 @@
1
1
  import { calculateAnimationPlan } from '../../../../animation/planner.js';
2
2
  import { piecePositionsEqual } from '../../../../state/board/check.js';
3
+ import { changeStatesEqual } from '../../../../state/change/helpers.js';
3
4
  import { isUpdateContextRenderable } from '../../../types/context/update.js';
4
5
  const DEFAULT_ANIMATION_DURATION_MS = 180;
5
6
  export function rendererAnimationOnUpdate(state, context) {
6
7
  if (!isUpdateContextRenderable(context) ||
7
- !context.mutation.hasMutation({ prefixes: ['state.board.'] }) ||
8
+ !context.mutation.hasMutation({ prefixes: ['state.board.', 'state.change.'] }) ||
8
9
  !context.previousFrame) {
9
10
  return;
10
11
  }
11
- const previousBoard = context.previousFrame.state.board;
12
- const currentBoard = context.currentFrame.state.board;
13
- if (piecePositionsEqual(previousBoard, currentBoard))
12
+ if (piecePositionsEqual(context.previousFrame.state.board, context.currentFrame.state.board) &&
13
+ changeStatesEqual(context.previousFrame.state.change, context.currentFrame.state.change))
14
14
  return;
15
- // On DropTo exclude the move that user did from the animation plan
16
- const exclude = [];
17
- if (context.mutation.hasMutation({ causes: ['runtime.interaction.dropTo'] }) &&
18
- context.currentFrame.state.change.lastMove) {
19
- exclude.push({
20
- fromSq: context.currentFrame.state.change.lastMove.from,
21
- toSq: context.currentFrame.state.change.lastMove.to
22
- });
23
- exclude.push({
24
- sq: context.currentFrame.state.change.lastMove.from
25
- });
26
- exclude.push({
27
- sq: context.currentFrame.state.change.lastMove.to
28
- });
29
- }
30
- const plan = calculateAnimationPlan(previousBoard, currentBoard, 0 /* placeholder session id for draft plan */, {
31
- exclude
15
+ // Pass raw snapshots to the planner — it normalizes internally
16
+ const plan = calculateAnimationPlan({
17
+ previous: context.previousFrame.state,
18
+ current: context.currentFrame.state
32
19
  });
33
20
  if (plan.tracks.length === 0)
34
21
  return;
35
22
  const session = state.runtimeSurface.animation.submit({
36
23
  duration: DEFAULT_ANIMATION_DURATION_MS
37
24
  });
38
- plan.sessionId = session.id;
39
- state.entries.set(plan.sessionId, { plan, nodes: null });
25
+ state.entries.set(session.id, { plan, nodes: null });
40
26
  }
@@ -10,13 +10,13 @@ export function rendererBoardRender(state, context, layer) {
10
10
  clearElementChildren(layer);
11
11
  const { light, dark } = state.config;
12
12
  for (let sq = 0; sq < SQUARE_COUNT; sq++) {
13
- const r = geometry.squareRect(sq);
13
+ const r = geometry.getSquareRect(sq);
14
14
  createSvgElement(layer, 'rect', {
15
15
  'data-chessboard-id': `square-${sq}`,
16
16
  x: r.x.toString(),
17
17
  y: r.y.toString(),
18
- width: r.size.toString(),
19
- height: r.size.toString(),
18
+ width: r.width.toString(),
19
+ height: r.height.toString(),
20
20
  fill: isLightSquare(sq) ? light : dark,
21
21
  'shape-rendering': 'crispEdges'
22
22
  });
@@ -1,3 +1,4 @@
1
+ import { sceneRectsEqual } from '../../../../layout/geometry/helpers.js';
1
2
  import { isFrameRenderable } from '../../../types/basic/update.js';
2
3
  import { isUpdateContextRenderable } from '../../../types/context/update.js';
3
4
  import { DirtyLayer } from '../types/extension.js';
@@ -12,14 +13,15 @@ export function rendererBoardOnUpdate(_state, context) {
12
13
  }
13
14
  // Ok geometry changed, let's check the board size
14
15
  const currentGeometry = context.currentFrame.layout.geometry;
15
- const currentBoardSize = currentGeometry.boardSize;
16
+ const currentBoardRect = currentGeometry.boardRect;
16
17
  const currentOrientation = currentGeometry.orientation;
17
18
  const previousGeometry = context.previousFrame && isFrameRenderable(context.previousFrame)
18
19
  ? context.previousFrame.layout.geometry
19
20
  : null;
20
- const previousBoardSize = previousGeometry?.boardSize;
21
+ const previousBoardRect = previousGeometry?.boardRect ?? null;
21
22
  const previousOrientation = previousGeometry?.orientation;
22
- const needsRender = currentBoardSize !== previousBoardSize || currentOrientation !== previousOrientation;
23
+ const needsRender = sceneRectsEqual(currentBoardRect, previousBoardRect) === false ||
24
+ currentOrientation !== previousOrientation;
23
25
  if (!needsRender) {
24
26
  return; // no-op
25
27
  }
@@ -19,7 +19,7 @@ export function rendererCoordinatesRender(state, context, layer) {
19
19
  const logicalRank = (geometry.orientation === 0 /* ColorCode.White */ ? 7 - visualRank : visualRank);
20
20
  const sq = squareOf(rankFile, logicalRank);
21
21
  const label = geometry.orientation === 0 /* ColorCode.White */ ? String(8 - visualRank) : String(1 + visualRank);
22
- const r = geometry.squareRect(sq);
22
+ const r = geometry.getSquareRect(sq);
23
23
  const color = labelColorForSquare(sq, coords);
24
24
  const text = createSvgElement(layer, 'text', {
25
25
  'data-chessboard-id': `coord-rank-${label}`,
@@ -42,12 +42,12 @@ export function rendererCoordinatesRender(state, context, layer) {
42
42
  const label = geometry.orientation === 0 /* ColorCode.White */
43
43
  ? String.fromCharCode('a'.charCodeAt(0) + visualFile)
44
44
  : String.fromCharCode('h'.charCodeAt(0) - visualFile);
45
- const r = geometry.squareRect(sq);
45
+ const r = geometry.getSquareRect(sq);
46
46
  const color = labelColorForSquare(sq, coords);
47
47
  const text = createSvgElement(layer, 'text', {
48
48
  'data-chessboard-id': `coord-file-${label}`,
49
- x: (r.x + r.size - offset).toString(),
50
- y: (r.y + r.size - offset).toString(),
49
+ x: (r.x + r.width - offset).toString(),
50
+ y: (r.y + r.height - offset).toString(),
51
51
  'font-size': fontSize.toString(),
52
52
  'font-family': 'sans-serif',
53
53
  'font-weight': 'bold',
@@ -1,7 +1,7 @@
1
1
  import { createSvgElement, updateElementAttributes } from '../../../../render/svg/helpers.js';
2
2
  export function rendererDragRenderTransientVisuals(state, context, layer) {
3
3
  const geometry = context.currentFrame.layout.geometry;
4
- const point = context.transientInput.clampedPoint;
4
+ const point = context.transientInput.boardClampedPoint;
5
5
  const squareSize = geometry.squareSize.toString();
6
6
  const x = (point.x - geometry.squareSize / 2).toString();
7
7
  const y = (point.y - geometry.squareSize / 2).toString();
@@ -1,4 +1,3 @@
1
- import { MainRendererConfigInput, PieceUrlsInput } from './types/input.js';
2
- import { MainRendererConfig, PieceUrls } from './types/internal.js';
3
- export declare function normalizePieceUrls(input: PieceUrlsInput): PieceUrls;
1
+ import { MainRendererConfigInput } from './types/input.js';
2
+ import { MainRendererConfig } from './types/internal.js';
4
3
  export declare function normalizeMainRendererConfig(input: Partial<MainRendererConfigInput>): MainRendererConfig;
@@ -7,7 +7,7 @@ function assertPieceUrlsComplete(pieceUrls) {
7
7
  assert(pieceCode in pieceUrls, `Missing URL for piece code: ${pieceCode}`);
8
8
  }
9
9
  }
10
- export function normalizePieceUrls(input) {
10
+ function normalizePieceUrls(input) {
11
11
  const normalized = {};
12
12
  for (const [key, url] of Object.entries(input)) {
13
13
  const pieceCode = normalizePiece(key);