@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
@@ -14,13 +14,13 @@ export function rendererPiecesRender(state, context, layer) {
14
14
  const suppressed = state.suppressedSquares.has(sq);
15
15
  if (isNonEmptyPieceCode(pieceCode) && !suppressed) {
16
16
  const url = state.config[pieceCode];
17
- const r = geometry.squareRect(sq);
17
+ const r = geometry.getSquareRect(sq);
18
18
  if (existing !== null) {
19
19
  updateElementAttributes(existing.root, {
20
20
  x: r.x.toString(),
21
21
  y: r.y.toString(),
22
- width: r.size.toString(),
23
- height: r.size.toString(),
22
+ width: r.width.toString(),
23
+ height: r.height.toString(),
24
24
  href: url
25
25
  });
26
26
  }
@@ -31,8 +31,8 @@ export function rendererPiecesRender(state, context, layer) {
31
31
  href: url,
32
32
  x: r.x.toString(),
33
33
  y: r.y.toString(),
34
- width: r.size.toString(),
35
- height: r.size.toString()
34
+ width: r.width.toString(),
35
+ height: r.height.toString()
36
36
  })
37
37
  });
38
38
  }
@@ -1,9 +1,14 @@
1
1
  export function calculateSuppressedSquares(_state, context, animationSuppressedSquares) {
2
2
  const dragSession = context.currentFrame.state.interaction.dragSession;
3
- const dragSquare = dragSession?.type === 'lifted-piece-drag' ? dragSession.sourceSquare : undefined;
3
+ const dragSquare = dragSession?.type === 'lifted-piece-drag' ? dragSession.sourceSquare : null;
4
4
  const result = new Set(animationSuppressedSquares);
5
- if (dragSquare !== undefined) {
5
+ if (dragSquare !== null) {
6
6
  result.add(dragSquare);
7
7
  }
8
+ const pendingMoveRequest = context.currentFrame.state.change.deferredUIMoveRequest;
9
+ if (pendingMoveRequest) {
10
+ result.add(pendingMoveRequest.sourceSquare);
11
+ result.add(pendingMoveRequest.destination.to);
12
+ }
8
13
  return result;
9
14
  }
@@ -3,7 +3,7 @@ import { MainRendererConfigInput } from './input.js';
3
3
  export type MainRendererInitOptions = Partial<MainRendererConfigInput>;
4
4
  export declare const EXTENSION_SLOTS: readonly ["board", "coordinates", "pieces", "animation", "drag"];
5
5
  export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
6
- export declare const EXTENSION_ID: "main-renderer";
6
+ export declare const EXTENSION_ID: "renderer";
7
7
  export type MainRendererDefinition = ExtensionDefinition<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, never>;
8
8
  export type MainRendererInstance = ExtensionInstance<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, never>;
9
9
  export declare enum DirtyLayer {
@@ -1,5 +1,5 @@
1
1
  export const EXTENSION_SLOTS = ['board', 'coordinates', 'pieces', 'animation', 'drag'];
2
- export const EXTENSION_ID = 'main-renderer';
2
+ export const EXTENSION_ID = 'renderer';
3
3
  export var DirtyLayer;
4
4
  (function (DirtyLayer) {
5
5
  DirtyLayer[DirtyLayer["Board"] = 1] = "Board";
@@ -0,0 +1,3 @@
1
+ import { PromotionInitConfig } from './types/input.js';
2
+ import { PromotionDefinition } from './types/main.js';
3
+ export declare function createPromotion(config?: PromotionInitConfig): PromotionDefinition;
@@ -0,0 +1,219 @@
1
+ import assert from '@ktarmyshov/assert';
2
+ import { createSvgElement, updateElementAttributes } from '../../../render/svg/helpers.js';
3
+ import { fileOf, rankOf, squareOf } from '../../../state/board/coords.js';
4
+ import { toPieceCode } from '../../../state/board/piece.js';
5
+ import { isUpdateContextRenderable } from '../../types/context/update.js';
6
+ import { extensionCreateInternalBase, extensionDestroy, extensionMount, extensionUnmount } from '../common/helpers.js';
7
+ import { normalizePromotionConfig } from './normalize.js';
8
+ import { EXTENSION_ID, EXTENSION_SLOTS } from './types/main.js';
9
+ export function createPromotion(config = {}) {
10
+ const mergedConfig = normalizePromotionConfig(config);
11
+ return {
12
+ id: EXTENSION_ID,
13
+ slots: EXTENSION_SLOTS,
14
+ createInstance(options) {
15
+ return createPromotionInstance(options, mergedConfig);
16
+ }
17
+ };
18
+ }
19
+ function createPromotionInternal(options, config) {
20
+ return {
21
+ ...extensionCreateInternalBase(),
22
+ svgPromotionPieces: new Map(),
23
+ activePromotionSquares: new Map(),
24
+ svgHoverRect: null,
25
+ config,
26
+ runtimeSurface: options.runtimeSurface
27
+ };
28
+ }
29
+ function extensionCleanPieceNode(state, roleCode) {
30
+ const pieceNode = state.svgPromotionPieces.get(roleCode);
31
+ if (pieceNode) {
32
+ pieceNode.svg.remove();
33
+ pieceNode.rect.remove();
34
+ state.svgPromotionPieces.delete(roleCode);
35
+ }
36
+ }
37
+ function extensionCleanPieceNodes(state) {
38
+ for (const pieceNode of state.svgPromotionPieces.values()) {
39
+ pieceNode.svg.remove();
40
+ pieceNode.rect.remove();
41
+ }
42
+ state.svgPromotionPieces.clear();
43
+ }
44
+ function extensionCleanHoverRect(state) {
45
+ if (state.svgHoverRect) {
46
+ state.svgHoverRect.remove();
47
+ state.svgHoverRect = null;
48
+ }
49
+ }
50
+ function extensionClean(state) {
51
+ extensionCleanPieceNodes(state);
52
+ extensionCleanHoverRect(state);
53
+ state.activePromotionSquares.clear();
54
+ }
55
+ function createPromotionInstance(options, config) {
56
+ const internalState = createPromotionInternal(options, config);
57
+ return {
58
+ id: EXTENSION_ID,
59
+ mount(env) {
60
+ extensionMount(internalState, env.slotRoots);
61
+ },
62
+ onUIMoveRequest(context) {
63
+ const request = context.request;
64
+ if (request.status !== 'unresolved')
65
+ return; // no-op if the request is already resolved or deferred
66
+ if (request.canBeAutoResolved)
67
+ return; // no-op, core will auto-resolve
68
+ // OK! now this one is our request!
69
+ request.defer(); // defer the request until the user selects a piece to promote to
70
+ },
71
+ onUpdate(context) {
72
+ const needsRender = context.mutation.hasMutation({
73
+ causes: ['state.change.setDeferredUIMoveRequest', 'layout.refreshGeometry']
74
+ }) && isUpdateContextRenderable(context);
75
+ if (!needsRender) {
76
+ return; // no-op
77
+ }
78
+ const request = context.currentFrame.state.change.deferredUIMoveRequest;
79
+ if (request !== null) {
80
+ internalState.runtimeSurface.transientVisuals.subscribe();
81
+ internalState.runtimeSurface.events.subscribeEvent('pointerdown');
82
+ }
83
+ else {
84
+ internalState.runtimeSurface.transientVisuals.unsubscribe();
85
+ internalState.runtimeSurface.events.unsubscribeEvent('pointerdown');
86
+ }
87
+ context.invalidation.markDirty(1 /* DirtyLayer.Promotion */);
88
+ },
89
+ render(context) {
90
+ assert(context.invalidation.dirtyLayers !== 0, 'Render should only be called when there are dirty layers');
91
+ const deferredRequest = context.currentFrame.state.change.deferredUIMoveRequest;
92
+ if (deferredRequest === null) {
93
+ // No deferred request, so we can clean up any existing promotion piece nodes and highlights
94
+ extensionClean(internalState);
95
+ return;
96
+ }
97
+ const targetSquare = deferredRequest.destination.to;
98
+ const targetFile = fileOf(targetSquare);
99
+ const targetRank = rankOf(targetSquare);
100
+ const promotedTo = deferredRequest.destination.promotedTo;
101
+ assert(promotedTo && promotedTo.length > 1 && promotedTo.length <= 4, 'PromotedTo should be available in a deferred promotion request and have between 2 and 4 roles');
102
+ const promotedToSorted = promotedTo.toSorted((a, b) => b - a); // Descending order to render queen on top
103
+ const geometry = context.currentFrame.layout.geometry;
104
+ // Determine piece color from target rank
105
+ const isWhitePromotion = targetRank === 7;
106
+ const pieceColor = isWhitePromotion ? 0 /* ColorCode.White */ : 8 /* ColorCode.Black */;
107
+ // Direction: white pieces cascade downward (rank decreases), black upward (rank increases)
108
+ const rankStep = isWhitePromotion ? -1 : 1;
109
+ // Rebuild the active promotion squares set
110
+ internalState.activePromotionSquares.clear();
111
+ // Remove nodes for roles no longer in the desired set
112
+ const desiredRoles = new Set(promotedToSorted);
113
+ for (const [roleCode] of internalState.svgPromotionPieces) {
114
+ if (!desiredRoles.has(roleCode)) {
115
+ extensionCleanPieceNode(internalState, roleCode);
116
+ }
117
+ }
118
+ // Create or update a node for each promotion role
119
+ assert(internalState.slotRoots, 'Slot roots should be available when render is called');
120
+ const layer = internalState.slotRoots.animation;
121
+ for (let i = 0; i < promotedToSorted.length; i++) {
122
+ const roleCode = promotedToSorted[i];
123
+ const displayRank = (targetRank + i * rankStep);
124
+ const displaySquare = squareOf(targetFile, displayRank);
125
+ const pieceCode = toPieceCode(roleCode, pieceColor);
126
+ const url = internalState.config.pieceUrls[pieceCode];
127
+ const r = geometry.getSquareRect(displaySquare);
128
+ const rectAttrs = {
129
+ x: r.x.toString(),
130
+ y: r.y.toString(),
131
+ width: r.width.toString(),
132
+ height: r.height.toString(),
133
+ fill: internalState.config.squareColor.color,
134
+ 'fill-opacity': internalState.config.squareColor.opacity.toString(),
135
+ 'shape-rendering': 'crispEdges'
136
+ };
137
+ const imageAttrs = {
138
+ href: url,
139
+ x: r.x.toString(),
140
+ y: r.y.toString(),
141
+ width: r.width.toString(),
142
+ height: r.height.toString()
143
+ };
144
+ internalState.activePromotionSquares.set(displaySquare, roleCode);
145
+ const existing = internalState.svgPromotionPieces.get(roleCode) ?? null;
146
+ if (existing !== null) {
147
+ updateElementAttributes(existing.rect, rectAttrs);
148
+ updateElementAttributes(existing.svg, imageAttrs);
149
+ }
150
+ else {
151
+ const rect = createSvgElement(layer, 'rect', {
152
+ 'data-chessboard-id': `promotion-bg-${roleCode}`,
153
+ ...rectAttrs
154
+ });
155
+ const svg = createSvgElement(layer, 'image', {
156
+ 'data-chessboard-id': `promotion-piece-${roleCode}`,
157
+ ...imageAttrs
158
+ });
159
+ internalState.svgPromotionPieces.set(roleCode, { svg, rect });
160
+ }
161
+ }
162
+ },
163
+ renderTransientVisuals(context) {
164
+ const target = context.transientInput.target;
165
+ if (target !== null && internalState.activePromotionSquares.has(target)) {
166
+ // Hover is over one of the promotion squares
167
+ const geometry = context.currentFrame.layout.geometry;
168
+ const r = geometry.getSquareRect(target);
169
+ const attrs = {
170
+ x: r.x.toString(),
171
+ y: r.y.toString(),
172
+ width: r.width.toString(),
173
+ height: r.height.toString(),
174
+ fill: internalState.config.hoverColor.color,
175
+ 'fill-opacity': internalState.config.hoverColor.opacity.toString(),
176
+ 'shape-rendering': 'crispEdges'
177
+ };
178
+ if (internalState.svgHoverRect) {
179
+ updateElementAttributes(internalState.svgHoverRect, attrs);
180
+ }
181
+ else {
182
+ assert(internalState.slotRoots, 'Slot roots should be available when renderTransientVisuals is called');
183
+ const layer = internalState.slotRoots.animation;
184
+ internalState.svgHoverRect = createSvgElement(layer, 'rect', {
185
+ 'data-chessboard-id': 'promotion-hover',
186
+ ...attrs
187
+ });
188
+ }
189
+ }
190
+ else {
191
+ // Not hovering over a promotion square — remove hover rect
192
+ extensionCleanHoverRect(internalState);
193
+ }
194
+ },
195
+ onEvent(context) {
196
+ assert(context.rawEvent.type === 'pointerdown', 'Only pointerdown events should be subscribed to');
197
+ const targetSquare = context.sceneEvent?.targetSquare ?? null;
198
+ if (targetSquare !== null) {
199
+ const roleCode = internalState.activePromotionSquares.get(targetSquare);
200
+ if (roleCode !== undefined) {
201
+ context.rawEvent.preventDefault(); // Prevent focus change and other side effects if it is our promotion square
202
+ internalState.runtimeSurface.commands.resolveDeferredUIMoveRequest({
203
+ promotedTo: roleCode
204
+ });
205
+ return;
206
+ }
207
+ }
208
+ internalState.runtimeSurface.commands.cancelDeferredUIMoveRequest();
209
+ },
210
+ unmount() {
211
+ extensionUnmount(internalState);
212
+ extensionClean(internalState);
213
+ },
214
+ destroy() {
215
+ extensionDestroy(internalState);
216
+ extensionClean(internalState);
217
+ }
218
+ };
219
+ }
@@ -0,0 +1,3 @@
1
+ import { PromotionInitConfig } from './types/input.js';
2
+ import { PromotionConfig } from './types/internal.js';
3
+ export declare function normalizePromotionConfig(config: PromotionInitConfig): PromotionConfig;
@@ -0,0 +1,37 @@
1
+ import assert from '@ktarmyshov/assert';
2
+ import { normalizePiece } from '../../../state/board/normalize.js';
3
+ import { ALL_NON_EMPTY_PROMOTION_PIECE_CODES, DEFAULT_CONFIG } from './types/internal.js';
4
+ function assertPieceUrlsComplete(pieceUrls) {
5
+ for (const pieceCode of ALL_NON_EMPTY_PROMOTION_PIECE_CODES) {
6
+ assert(pieceCode in pieceUrls, `Missing URL for piece code: ${pieceCode}`);
7
+ }
8
+ }
9
+ function assertPieceCodeIsPromotionPieceCode(pieceCode) {
10
+ assert(pieceCode === 5 /* PieceCode.WhiteQueen */ ||
11
+ pieceCode === 4 /* PieceCode.WhiteRook */ ||
12
+ pieceCode === 3 /* PieceCode.WhiteBishop */ ||
13
+ pieceCode === 2 /* PieceCode.WhiteKnight */ ||
14
+ pieceCode === 13 /* PieceCode.BlackQueen */ ||
15
+ pieceCode === 12 /* PieceCode.BlackRook */ ||
16
+ pieceCode === 11 /* PieceCode.BlackBishop */ ||
17
+ pieceCode === 10 /* PieceCode.BlackKnight */, `Invalid promotion piece code: ${pieceCode}`);
18
+ }
19
+ function normalizePromotionPieceUrls(input) {
20
+ const normalized = {};
21
+ for (const [key, url] of Object.entries(input)) {
22
+ const pieceCode = normalizePiece(key);
23
+ assertPieceCodeIsPromotionPieceCode(pieceCode);
24
+ normalized[pieceCode] = url;
25
+ }
26
+ assertPieceUrlsComplete(normalized);
27
+ return normalized;
28
+ }
29
+ export function normalizePromotionConfig(config) {
30
+ return {
31
+ squareColor: config.squareColor ?? DEFAULT_CONFIG.squareColor,
32
+ hoverColor: config.hoverColor ?? DEFAULT_CONFIG.hoverColor,
33
+ pieceUrls: config.pieceUrls
34
+ ? normalizePromotionPieceUrls(config.pieceUrls)
35
+ : DEFAULT_CONFIG.pieceUrls
36
+ };
37
+ }
@@ -0,0 +1,10 @@
1
+ import { ColorShort, RolePromotionShort } from '../../../../state/board/types/input.js';
2
+ import { OpaqueColor } from '../../common/types.js';
3
+ import { PieceUrlsInput } from '../../main-renderer/types/input.js';
4
+ export type PromotionPieceString = `${ColorShort}${RolePromotionShort}`;
5
+ export type PromotionPieceUrlsInput = Pick<PieceUrlsInput, PromotionPieceString>;
6
+ export interface PromotionInitConfig {
7
+ squareColor?: OpaqueColor;
8
+ hoverColor?: OpaqueColor;
9
+ pieceUrls?: PromotionPieceUrlsInput;
10
+ }
@@ -0,0 +1,12 @@
1
+ import { NonEmptyPieceCode, PieceCode } from '../../../../state/board/types/internal.js';
2
+ import { OpaqueColor } from '../../common/types.js';
3
+ export type PromotionPieceCode = Exclude<NonEmptyPieceCode, PieceCode.WhitePawn | PieceCode.BlackPawn | PieceCode.BlackKing | PieceCode.WhiteKing>;
4
+ export type PromotionPieceUrls = Record<PromotionPieceCode, string>;
5
+ export interface PromotionConfig {
6
+ squareColor: OpaqueColor;
7
+ hoverColor: OpaqueColor;
8
+ pieceUrls: PromotionPieceUrls;
9
+ }
10
+ export declare const PROMOTION_CHESSNUT_PIECE_URLS: PromotionPieceUrls;
11
+ export declare const ALL_NON_EMPTY_PROMOTION_PIECE_CODES: PromotionPieceCode[];
12
+ export declare const DEFAULT_CONFIG: PromotionConfig;
@@ -0,0 +1,22 @@
1
+ export const PROMOTION_CHESSNUT_PIECE_URLS = {
2
+ [5 /* PieceCode.WhiteQueen */]: new URL('../../../../../assets/pieces/chessnut/wQ.svg', import.meta.url).toString(),
3
+ [4 /* PieceCode.WhiteRook */]: new URL('../../../../../assets/pieces/chessnut/wR.svg', import.meta.url).toString(),
4
+ [3 /* PieceCode.WhiteBishop */]: new URL('../../../../../assets/pieces/chessnut/wB.svg', import.meta.url).toString(),
5
+ [2 /* PieceCode.WhiteKnight */]: new URL('../../../../../assets/pieces/chessnut/wN.svg', import.meta.url).toString(),
6
+ [13 /* PieceCode.BlackQueen */]: new URL('../../../../../assets/pieces/chessnut/bQ.svg', import.meta.url).toString(),
7
+ [12 /* PieceCode.BlackRook */]: new URL('../../../../../assets/pieces/chessnut/bR.svg', import.meta.url).toString(),
8
+ [11 /* PieceCode.BlackBishop */]: new URL('../../../../../assets/pieces/chessnut/bB.svg', import.meta.url).toString(),
9
+ [10 /* PieceCode.BlackKnight */]: new URL('../../../../../assets/pieces/chessnut/bN.svg', import.meta.url).toString()
10
+ };
11
+ export const ALL_NON_EMPTY_PROMOTION_PIECE_CODES = Object.keys(PROMOTION_CHESSNUT_PIECE_URLS).map((code) => Number(code));
12
+ export const DEFAULT_CONFIG = {
13
+ squareColor: {
14
+ color: 'rgba(255, 255, 255)',
15
+ opacity: 1
16
+ },
17
+ hoverColor: {
18
+ color: '#000000',
19
+ opacity: 0.15
20
+ },
21
+ pieceUrls: PROMOTION_CHESSNUT_PIECE_URLS
22
+ };
@@ -0,0 +1,24 @@
1
+ import { RolePromotionCode, Square } from '../../../../state/board/types/internal.js';
2
+ import { ExtensionDefinition, ExtensionInstance } from '../../../types/extension.js';
3
+ import { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
4
+ import { ExtensionInternal } from '../../common/types.js';
5
+ import { PromotionConfig } from './internal.js';
6
+ export declare const EXTENSION_SLOTS: readonly ["animation"];
7
+ export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
8
+ export declare const EXTENSION_ID: "promotion";
9
+ export type PromotionDefinition = ExtensionDefinition<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, never>;
10
+ export type PromotionInstance = ExtensionInstance<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, never>;
11
+ export interface PromotionPieceNode {
12
+ readonly svg: SVGImageElement;
13
+ readonly rect: SVGRectElement;
14
+ }
15
+ export interface PromotionInstanceInternal extends ExtensionInternal<ExtensionSlotsType> {
16
+ readonly config: PromotionConfig;
17
+ readonly svgPromotionPieces: Map<RolePromotionCode, PromotionPieceNode>;
18
+ readonly activePromotionSquares: Map<Square, RolePromotionCode>;
19
+ svgHoverRect: SVGRectElement | null;
20
+ readonly runtimeSurface: ExtensionRuntimeSurface;
21
+ }
22
+ export declare const enum DirtyLayer {
23
+ Promotion = 1
24
+ }
@@ -0,0 +1,2 @@
1
+ export const EXTENSION_SLOTS = ['animation'];
2
+ export const EXTENSION_ID = 'promotion';
@@ -55,12 +55,12 @@ function createSelectedSquareInstance(config) {
55
55
  return;
56
56
  }
57
57
  const geometry = context.currentFrame.layout.geometry;
58
- const r = geometry.squareRect(selectedSquare);
58
+ const r = geometry.getSquareRect(selectedSquare);
59
59
  const rectAttributes = {
60
60
  x: r.x.toString(),
61
61
  y: r.y.toString(),
62
- width: r.size.toString(),
63
- height: r.size.toString(),
62
+ width: r.width.toString(),
63
+ height: r.height.toString(),
64
64
  fill: internalState.config.color,
65
65
  'fill-opacity': internalState.config.opacity.toString(),
66
66
  'shape-rendering': 'crispEdges'
@@ -5,4 +5,4 @@ export { createLegalMoves } from './first-party/legal-moves/factory.js';
5
5
  export { createMainRenderer } from './first-party/main-renderer/factory.js';
6
6
  export { createSelectedSquare } from './first-party/selected-square/factory.js';
7
7
  export type { AnyExtensionDefinition, ExtensionDefinitionId, ExtensionDefinitionPublicApi } from './types/extension.js';
8
- export type { BuiltInExtensionId, DefaultBuiltinChessboardExtensions } from './types/wrapper.js';
8
+ export type { builtInExtensionFactoryMap, BuiltInExtensionId } from './types/wrapper.js';
@@ -1,34 +1,11 @@
1
1
  import { Square } from '../../../state/board/types/internal.js';
2
2
  import { ScenePoint } from '../basic/transient-visuals.js';
3
- interface SceneEventBase {
4
- readonly defaultPrevented: boolean;
5
- preventDefault(): void;
3
+ export interface ScenePointerEvent {
4
+ readonly type: 'pointercancel' | 'pointerdown' | 'pointerenter' | 'pointerleave' | 'pointermove' | 'pointerout' | 'pointerover' | 'pointerrawupdate' | 'pointerup';
5
+ readonly point: ScenePoint;
6
+ readonly clampedPoint: ScenePoint;
7
+ readonly boardClampedPoint: ScenePoint | null;
8
+ readonly targetSquare: Square | null;
6
9
  }
7
- export interface ScenePointerEvent extends SceneEventBase {
8
- readonly type: 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel' | 'pointerleave';
9
- readonly pointerId: number;
10
- readonly isPrimary: boolean;
11
- readonly button: number;
12
- readonly buttons: number;
13
- readonly ctrlKey: boolean;
14
- readonly altKey: boolean;
15
- readonly shiftKey: boolean;
16
- readonly metaKey: boolean;
17
- readonly rawPoint: ScenePoint | null;
18
- readonly clampedPoint: ScenePoint | null;
19
- readonly target: Square | null;
20
- }
21
- export type ScenePointerEventType = ScenePointerEvent['type'];
22
- export declare const SCENE_POINTER_EVENT_TYPES: ScenePointerEventType[];
23
- export interface SceneKeyboardEvent extends SceneEventBase {
24
- readonly type: 'keydown' | 'keyup';
25
- readonly key: string;
26
- readonly repeat: boolean;
27
- readonly ctrlKey: boolean;
28
- readonly altKey: boolean;
29
- readonly shiftKey: boolean;
30
- readonly metaKey: boolean;
31
- }
32
- export type SceneEvent = ScenePointerEvent | SceneKeyboardEvent;
33
- export type SceneEventType = SceneEvent['type'];
34
- export {};
10
+ export declare const ALL_SCENE_POINTER_EVENT_TYPES: ReadonlySet<string>;
11
+ export type SceneEvent = ScenePointerEvent;
@@ -1,7 +1,11 @@
1
- export const SCENE_POINTER_EVENT_TYPES = [
1
+ export const ALL_SCENE_POINTER_EVENT_TYPES = new Set([
2
+ 'pointercancel',
2
3
  'pointerdown',
4
+ 'pointerenter',
5
+ 'pointerleave',
3
6
  'pointermove',
4
- 'pointerup',
5
- 'pointercancel',
6
- 'pointerleave'
7
- ];
7
+ 'pointerout',
8
+ 'pointerover',
9
+ 'pointerrawupdate',
10
+ 'pointerup'
11
+ ]);
@@ -0,0 +1,21 @@
1
+ import { ReadonlyDeep } from 'type-fest';
2
+ import type { PieceCode, Square } from '../../../state/board/types/internal.js';
3
+ export interface ExtensionDragSessionBase {
4
+ sourceSquare: Square | null;
5
+ sourcePieceCode: PieceCode | null;
6
+ targetSquare: Square | null;
7
+ }
8
+ export interface ExtensionDragSessionLiftedPiece extends ExtensionDragSessionBase {
9
+ type: 'lifted-piece-drag';
10
+ sourcePieceCode: PieceCode;
11
+ }
12
+ export interface ExtensionDragSessionReleaseTargeting extends ExtensionDragSessionBase {
13
+ type: 'release-targeting';
14
+ }
15
+ type ExtensionCustomDragSessionType = `ext:${string}`;
16
+ export interface ExtensionDragSessionCustom extends ExtensionDragSessionBase {
17
+ type: ExtensionCustomDragSessionType;
18
+ }
19
+ export type ExtensionDragSession = ExtensionDragSessionLiftedPiece | ExtensionDragSessionReleaseTargeting | ExtensionDragSessionCustom;
20
+ export type ExtensionDragSessionSnapshot = ReadonlyDeep<ExtensionDragSession>;
21
+ export {};
@@ -5,6 +5,7 @@ export interface ScenePoint {
5
5
  }
6
6
  export interface TransientInput {
7
7
  target: Square | null;
8
- rawPoint: ScenePoint;
8
+ point: ScenePoint;
9
9
  clampedPoint: ScenePoint;
10
+ boardClampedPoint: ScenePoint;
10
11
  }
@@ -0,0 +1,5 @@
1
+ import { SceneEvent } from '../basic/events.js';
2
+ export interface ExtensionOnEventContext {
3
+ rawEvent: Event;
4
+ sceneEvent: SceneEvent | null;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,16 +1,4 @@
1
- import { MoveRequest, Square } from '../../../state/board/types/internal.js';
2
- import { MoveDestinationSnapshot } from '../../../state/interaction/types/internal.js';
3
- export type ExtensionUIMoveRequestStatus = 'unresolved' | 'deferred' | 'resolved';
4
- export interface ExtensionUIMoveRequestContextSnapshot {
5
- readonly status: ExtensionUIMoveRequestStatus;
6
- readonly sourceSquare: Square;
7
- readonly destination: MoveDestinationSnapshot;
8
- readonly canBeAutoResolved: boolean;
9
- readonly resolvedMoveRequest: MoveRequest | null;
10
- }
11
- export interface ExtensionUIMoveRequestContext extends ExtensionUIMoveRequestContextSnapshot {
12
- defer(): void;
13
- resolve(request: MoveRequest): void;
14
- autoresolve(): void;
15
- getSnapshot(): ExtensionUIMoveRequestContextSnapshot;
1
+ import { PendingUIMoveRequest } from '../../../state/change/types/ui-move.js';
2
+ export interface ExtensionPendingUIMoveRequestContext {
3
+ readonly request: PendingUIMoveRequest;
16
4
  }
@@ -1,25 +1,27 @@
1
- import { SceneEvent } from './basic/events.js';
1
+ import { ExtensionDragSessionSnapshot } from './basic/interaction.js';
2
2
  import { ExtensionInstanceMountOptions, ExtensionSlotName } from './basic/mount.js';
3
3
  import { ExtensionAnimationFinishedContext, ExtensionCleanAnimationContext, ExtensionPrepareAnimationContext, ExtensionRenderAnimationContext } from './context/animation.js';
4
+ import { ExtensionOnEventContext } from './context/events.js';
4
5
  import { ExtensionRenderContext } from './context/render.js';
5
6
  import { ExtensionRenderTransientVisualsContext } from './context/transient-visuals.js';
6
- import { ExtensionUIMoveRequestContext } from './context/ui-move.js';
7
+ import { ExtensionPendingUIMoveRequestContext } from './context/ui-move.js';
7
8
  import { ExtensionUpdateContext } from './context/update.js';
8
9
  import { ExtensionRuntimeSurface } from './surface/main.js';
9
10
  interface ExtensionInstanceBase<TId extends string, TSlots extends readonly ExtensionSlotName[]> {
10
11
  readonly id: TId;
11
- mount(env: ExtensionInstanceMountOptions<TSlots>): void;
12
- unmount(): void;
13
- destroy(): void;
14
- onUpdate(context: ExtensionUpdateContext): void;
15
- onUIMoveRequest?(context: ExtensionUIMoveRequestContext): void;
12
+ mount?(env: ExtensionInstanceMountOptions<TSlots>): void;
13
+ unmount?(): void;
14
+ destroy?(): void;
15
+ onUpdate?(context: ExtensionUpdateContext): void;
16
16
  render?(context: ExtensionRenderContext): void;
17
17
  prepareAnimation?(context: ExtensionPrepareAnimationContext): void;
18
18
  renderAnimation?(context: ExtensionRenderAnimationContext): void;
19
19
  onAnimationFinished?(context: ExtensionAnimationFinishedContext): void;
20
20
  cleanAnimation?(context: ExtensionCleanAnimationContext): void;
21
+ completeDrag?(session: ExtensionDragSessionSnapshot): void;
21
22
  renderTransientVisuals?(context: ExtensionRenderTransientVisualsContext): void;
22
- onEvent?(event: SceneEvent): void;
23
+ onUIMoveRequest?(context: ExtensionPendingUIMoveRequestContext): void;
24
+ onEvent?(context: ExtensionOnEventContext): void;
23
25
  }
24
26
  type ExtensionInstancePublicPart<TPublic> = [TPublic] extends [never] ? Record<never, never> : {
25
27
  getPublic(): TPublic;
@@ -1,11 +1,13 @@
1
1
  import { RuntimeReadonlyMutationSession } from '../../runtime/mutation/types.js';
2
+ import { DragSessionExtensionOwned } from '../../state/interaction/types/internal.js';
2
3
  import { ExtensionInvalidationState } from '../invalidation/types.js';
3
4
  import { ExtensionAnimationControllerInternalSurface } from './basic/animation.js';
4
- import { SceneEventType } from './basic/events.js';
5
5
  import { UpdateFrameSnapshot } from './basic/update.js';
6
- import { ExtensionUIMoveRequestContext } from './context/ui-move.js';
6
+ import { ExtensionOnEventContext } from './context/events.js';
7
+ import { ExtensionPendingUIMoveRequestContext } from './context/ui-move.js';
7
8
  import { AnyExtensionDefinition, AnyExtensionInstance } from './extension.js';
8
- import { ExtensionRuntimeSurfaceCommands } from './surface/commands.js';
9
+ import { ExtensionRuntimeSurfaceCommandsInternalSurface } from './surface/commands.js';
10
+ import { ExtensionRuntimeSurfaceEvents } from './surface/events.js';
9
11
  export interface ExtensionSystemExtensionRecord {
10
12
  readonly id: string;
11
13
  readonly definition: AnyExtensionDefinition;
@@ -14,13 +16,14 @@ export interface ExtensionSystemExtensionRecord {
14
16
  readonly animation: ExtensionAnimationControllerInternalSurface;
15
17
  }
16
18
  export interface ExtensionSystemInitOptions {
17
- extensionRuntimeSurfaceCommands: ExtensionRuntimeSurfaceCommands;
19
+ extensionRuntimeSurfaceCommands: ExtensionRuntimeSurfaceCommandsInternalSurface;
20
+ extensionRuntimeSurfaceEvents: ExtensionRuntimeSurfaceEvents;
18
21
  extensions?: readonly AnyExtensionDefinition[];
19
22
  }
20
23
  export interface ExtensionSystemInternal {
21
24
  readonly extensions: Map<string, ExtensionSystemExtensionRecord>;
22
25
  readonly transientVisualsSubscribers: Set<string>;
23
- readonly eventSubscribers: Map<string, Set<SceneEventType>>;
26
+ readonly eventSubscribers: Map<string, Set<string>>;
24
27
  currentFrame: UpdateFrameSnapshot | null;
25
28
  }
26
29
  export interface ExtensionSystemUpdateRequest {
@@ -37,7 +40,9 @@ export interface ExtensionSystem {
37
40
  getPublicRecord(): Readonly<Record<string, unknown>>;
38
41
  getSharedDataForRenderSystem(): ExtensionSystemSharedDataForRenderSystem;
39
42
  onUpdate(request: ExtensionSystemUpdateRequest): void;
40
- onUIMoveRequest(context: ExtensionUIMoveRequestContext): void;
43
+ onUIMoveRequest(context: ExtensionPendingUIMoveRequestContext): void;
44
+ onEvent(context: ExtensionOnEventContext): void;
45
+ completeDrag(session: DragSessionExtensionOwned): void;
41
46
  onUnmount(): void;
42
47
  onDestroy(): void;
43
48
  }