@mirasen/chessboard 1.1.4 → 1.2.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 (135) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +7 -1
  3. package/assets/watermark/mirasen.svg +10 -0
  4. package/dist/animation/render/fade.d.ts +2 -3
  5. package/dist/animation/render/fade.js +6 -6
  6. package/dist/animation/render/move.d.ts +2 -3
  7. package/dist/animation/render/move.js +6 -6
  8. package/dist/animation/render/plan.d.ts +3 -4
  9. package/dist/animation/render/plan.js +5 -5
  10. package/dist/animation/render/static.d.ts +2 -3
  11. package/dist/animation/render/static.js +4 -4
  12. package/dist/animation/render/types.d.ts +10 -4
  13. package/dist/extensions/build/index.d.ts +1 -2
  14. package/dist/extensions/build/index.js +1 -1
  15. package/dist/extensions/factory/main.js +11 -7
  16. package/dist/extensions/factory/runtime.js +24 -1
  17. package/dist/extensions/first-party/active-target/factory.js +18 -11
  18. package/dist/extensions/first-party/annotations/api.d.ts +16 -0
  19. package/dist/extensions/first-party/annotations/api.js +79 -0
  20. package/dist/extensions/first-party/annotations/committed.d.ts +3 -0
  21. package/dist/extensions/first-party/annotations/committed.js +11 -0
  22. package/dist/extensions/first-party/annotations/constants.d.ts +3 -0
  23. package/dist/extensions/first-party/annotations/constants.js +54 -0
  24. package/dist/extensions/first-party/annotations/denormalize.d.ts +4 -0
  25. package/dist/extensions/first-party/annotations/denormalize.js +22 -0
  26. package/dist/extensions/first-party/annotations/factory.d.ts +3 -0
  27. package/dist/extensions/first-party/annotations/factory.js +143 -0
  28. package/dist/extensions/first-party/annotations/interaction.d.ts +8 -0
  29. package/dist/extensions/first-party/annotations/interaction.js +129 -0
  30. package/dist/extensions/first-party/annotations/invalidation.d.ts +3 -0
  31. package/dist/extensions/first-party/annotations/invalidation.js +7 -0
  32. package/dist/extensions/first-party/annotations/normalize.d.ts +12 -0
  33. package/dist/extensions/first-party/annotations/normalize.js +54 -0
  34. package/dist/extensions/first-party/annotations/render/committed.d.ts +3 -0
  35. package/dist/extensions/first-party/annotations/render/committed.js +123 -0
  36. package/dist/extensions/first-party/annotations/render/helpers/arrows.d.ts +33 -0
  37. package/dist/extensions/first-party/annotations/render/helpers/arrows.js +93 -0
  38. package/dist/extensions/first-party/annotations/render/helpers/circles.d.ts +18 -0
  39. package/dist/extensions/first-party/annotations/render/helpers/circles.js +30 -0
  40. package/dist/extensions/first-party/annotations/render/preview.d.ts +3 -0
  41. package/dist/extensions/first-party/annotations/render/preview.js +96 -0
  42. package/dist/extensions/first-party/annotations/types/internal.d.ts +77 -0
  43. package/dist/extensions/first-party/annotations/types/internal.js +1 -0
  44. package/dist/extensions/first-party/annotations/types/main.d.ts +40 -0
  45. package/dist/extensions/first-party/annotations/types/main.js +6 -0
  46. package/dist/extensions/first-party/annotations/types/public.d.ts +48 -0
  47. package/dist/extensions/first-party/annotations/types/public.js +1 -0
  48. package/dist/extensions/first-party/annotations/update.d.ts +3 -0
  49. package/dist/extensions/first-party/annotations/update.js +45 -0
  50. package/dist/extensions/first-party/auto-promote/factory.js +6 -6
  51. package/dist/extensions/first-party/board-events/factory.js +6 -6
  52. package/dist/extensions/first-party/common/helpers.d.ts +2 -1
  53. package/dist/extensions/first-party/common/helpers.js +6 -5
  54. package/dist/extensions/first-party/common/types.d.ts +2 -0
  55. package/dist/extensions/first-party/last-move/factory.js +9 -9
  56. package/dist/extensions/first-party/legal-moves/factory.js +8 -8
  57. package/dist/extensions/first-party/main-renderer/animation/factory.d.ts +2 -2
  58. package/dist/extensions/first-party/main-renderer/animation/factory.js +4 -4
  59. package/dist/extensions/first-party/main-renderer/animation/render.d.ts +1 -1
  60. package/dist/extensions/first-party/main-renderer/animation/render.js +2 -2
  61. package/dist/extensions/first-party/main-renderer/animation/types.d.ts +3 -3
  62. package/dist/extensions/first-party/main-renderer/board/factory.js +2 -2
  63. package/dist/extensions/first-party/main-renderer/board/render.d.ts +1 -1
  64. package/dist/extensions/first-party/main-renderer/board/render.js +4 -4
  65. package/dist/extensions/first-party/main-renderer/board/types.d.ts +1 -1
  66. package/dist/extensions/first-party/main-renderer/coordinates/factory.js +2 -2
  67. package/dist/extensions/first-party/main-renderer/coordinates/render.d.ts +1 -1
  68. package/dist/extensions/first-party/main-renderer/coordinates/render.js +5 -5
  69. package/dist/extensions/first-party/main-renderer/coordinates/types.d.ts +1 -1
  70. package/dist/extensions/first-party/main-renderer/drag/factory.d.ts +2 -2
  71. package/dist/extensions/first-party/main-renderer/drag/factory.js +11 -8
  72. package/dist/extensions/first-party/main-renderer/drag/render.d.ts +1 -1
  73. package/dist/extensions/first-party/main-renderer/drag/render.js +7 -6
  74. package/dist/extensions/first-party/main-renderer/drag/types.d.ts +6 -5
  75. package/dist/extensions/first-party/main-renderer/drag/update.js +2 -2
  76. package/dist/extensions/first-party/main-renderer/factory.js +9 -5
  77. package/dist/extensions/first-party/main-renderer/piece-symbols.d.ts +19 -0
  78. package/dist/extensions/first-party/main-renderer/piece-symbols.js +45 -0
  79. package/dist/extensions/first-party/main-renderer/pieces/factory.d.ts +2 -2
  80. package/dist/extensions/first-party/main-renderer/pieces/factory.js +4 -4
  81. package/dist/extensions/first-party/main-renderer/pieces/render.d.ts +1 -1
  82. package/dist/extensions/first-party/main-renderer/pieces/render.js +8 -8
  83. package/dist/extensions/first-party/main-renderer/pieces/types.d.ts +4 -4
  84. package/dist/extensions/first-party/main-renderer/types/extension.d.ts +1 -1
  85. package/dist/extensions/first-party/main-renderer/types/extension.js +8 -1
  86. package/dist/extensions/first-party/main-renderer/types/instance.d.ts +2 -0
  87. package/dist/extensions/first-party/promotion/factory.js +10 -10
  88. package/dist/extensions/first-party/selected-square/factory.js +8 -8
  89. package/dist/extensions/first-party/watermark/factory.d.ts +2 -0
  90. package/dist/extensions/first-party/watermark/factory.js +82 -0
  91. package/dist/extensions/first-party/watermark/types.d.ts +13 -0
  92. package/dist/extensions/first-party/watermark/types.js +2 -0
  93. package/dist/extensions/types/basic/events.d.ts +32 -3
  94. package/dist/extensions/types/basic/events.js +2 -1
  95. package/dist/extensions/types/basic/mount.d.ts +5 -2
  96. package/dist/extensions/types/basic/mount.js +0 -2
  97. package/dist/extensions/types/basic/transient-visuals.d.ts +1 -1
  98. package/dist/extensions/types/context/animation.d.ts +3 -3
  99. package/dist/extensions/types/context/events.d.ts +2 -1
  100. package/dist/extensions/types/context/update.d.ts +6 -7
  101. package/dist/extensions/types/extension.d.ts +3 -0
  102. package/dist/extensions/types/main.d.ts +1 -0
  103. package/dist/extensions/types/surface/commands.d.ts +1 -1
  104. package/dist/extensions/types/surface/main.d.ts +6 -4
  105. package/dist/extensions/types/wrapper.d.ts +5 -1
  106. package/dist/extensions/types/wrapper.js +10 -2
  107. package/dist/extensions/update.js +6 -10
  108. package/dist/index.d.ts +1 -1
  109. package/dist/render/mount.js +3 -3
  110. package/dist/render/rendering/state.js +2 -2
  111. package/dist/render/svg/factory.js +40 -25
  112. package/dist/render/svg/helpers.d.ts +8 -4
  113. package/dist/render/svg/helpers.js +27 -12
  114. package/dist/render/svg/ids.d.ts +6 -0
  115. package/dist/render/svg/ids.js +11 -0
  116. package/dist/render/types.d.ts +4 -3
  117. package/dist/render/types.js +2 -1
  118. package/dist/runtime/factory/input.d.ts +3 -1
  119. package/dist/runtime/factory/input.js +11 -2
  120. package/dist/runtime/factory/main.js +10 -4
  121. package/dist/runtime/input/adapter/event.js +2 -2
  122. package/dist/runtime/input/adapter/pointer.d.ts +2 -2
  123. package/dist/runtime/input/controller/factory.d.ts +1 -1
  124. package/dist/runtime/input/controller/factory.js +35 -15
  125. package/dist/runtime/input/controller/helpers.d.ts +1 -1
  126. package/dist/runtime/input/controller/interaction.d.ts +3 -0
  127. package/dist/runtime/input/controller/interaction.js +14 -0
  128. package/dist/runtime/input/controller/pointer.d.ts +6 -6
  129. package/dist/runtime/input/controller/pointer.js +53 -27
  130. package/dist/runtime/input/controller/transient-visuals.d.ts +2 -3
  131. package/dist/runtime/input/controller/types.d.ts +6 -5
  132. package/dist/runtime/types/main.d.ts +1 -1
  133. package/dist/wrapper/factory.js +2 -7
  134. package/dist/wrapper/types.d.ts +3 -11
  135. package/package.json +1 -3
@@ -9,8 +9,8 @@ export function createMainRendererBoard(config) {
9
9
  onUpdate(context) {
10
10
  rendererBoardOnUpdate(internalState, context);
11
11
  },
12
- render(context, layer) {
13
- rendererBoardRender(internalState, context, layer);
12
+ render(context, slot) {
13
+ rendererBoardRender(internalState, context, slot);
14
14
  }
15
15
  };
16
16
  }
@@ -1,3 +1,3 @@
1
1
  import { ExtensionRenderContext } from '../../../types/context/render.js';
2
2
  import { MainRendererBoardInternal } from './types.js';
3
- export declare function rendererBoardRender(state: MainRendererBoardInternal, context: ExtensionRenderContext, layer: SVGElement): void;
3
+ export declare function rendererBoardRender(state: MainRendererBoardInternal, context: ExtensionRenderContext, slot: SVGGElement): void;
@@ -1,17 +1,17 @@
1
- import { clearElementChildren, createSvgElement, isLightSquare } from '../../../../render/svg/helpers.js';
1
+ import { clearSvgElementChildren, createSvgElement, isLightSquare } from '../../../../render/svg/helpers.js';
2
2
  import { SQUARE_COUNT } from '../../../../state/board/types/internal.js';
3
3
  import { DirtyLayer } from '../types/extension.js';
4
- export function rendererBoardRender(state, context, layer) {
4
+ export function rendererBoardRender(state, context, slot) {
5
5
  // Check if we need to render
6
6
  if ((context.invalidation.dirtyLayers & DirtyLayer.Board) === 0) {
7
7
  return; // no-op
8
8
  }
9
9
  const geometry = context.currentFrame.layout.geometry;
10
- clearElementChildren(layer);
10
+ clearSvgElementChildren(slot);
11
11
  const { light, dark } = state.config;
12
12
  for (let sq = 0; sq < SQUARE_COUNT; sq++) {
13
13
  const r = geometry.getSquareRect(sq);
14
- createSvgElement(layer, 'rect', {
14
+ createSvgElement(slot, 'rect', {
15
15
  'data-chessboard-id': `square-${sq}`,
16
16
  x: r.x.toString(),
17
17
  y: r.y.toString(),
@@ -6,5 +6,5 @@ export interface MainRendererBoardInternal {
6
6
  }
7
7
  export interface MainRendererBoard {
8
8
  onUpdate(context: ExtensionUpdateContext): void;
9
- render(context: ExtensionRenderContext, layer: SVGElement): void;
9
+ render(context: ExtensionRenderContext, slot: SVGGElement): void;
10
10
  }
@@ -2,8 +2,8 @@ import { rendererCoordinatesRender } from './render.js';
2
2
  export function createMainRendererCoordinates(config) {
3
3
  const internalState = { config };
4
4
  return {
5
- render(context, layer) {
6
- rendererCoordinatesRender(internalState, context, layer);
5
+ render(context, slot) {
6
+ rendererCoordinatesRender(internalState, context, slot);
7
7
  }
8
8
  };
9
9
  }
@@ -1,3 +1,3 @@
1
1
  import { ExtensionRenderContext } from '../../../types/context/render.js';
2
2
  import { MainRendererCoordinatesInternal } from './types.js';
3
- export declare function rendererCoordinatesRender(state: MainRendererCoordinatesInternal, context: ExtensionRenderContext, layer: SVGElement): void;
3
+ export declare function rendererCoordinatesRender(state: MainRendererCoordinatesInternal, context: ExtensionRenderContext, slot: SVGGElement): void;
@@ -1,15 +1,15 @@
1
- import { clearElementChildren, createSvgElement, isLightSquare } from '../../../../render/svg/helpers.js';
1
+ import { clearSvgElementChildren, createSvgElement, isLightSquare } from '../../../../render/svg/helpers.js';
2
2
  import { squareOf } from '../../../../state/board/coords.js';
3
3
  import { DirtyLayer } from '../types/extension.js';
4
4
  function labelColorForSquare(sq, coordColors) {
5
5
  return isLightSquare(sq) ? coordColors.dark : coordColors.light;
6
6
  }
7
- export function rendererCoordinatesRender(state, context, layer) {
7
+ export function rendererCoordinatesRender(state, context, slot) {
8
8
  if ((context.invalidation.dirtyLayers & DirtyLayer.Coordinates) === 0) {
9
9
  return;
10
10
  }
11
11
  const geometry = context.currentFrame.layout.geometry;
12
- clearElementChildren(layer);
12
+ clearSvgElementChildren(slot);
13
13
  const coords = state.config;
14
14
  const fontSize = geometry.squareSize * 0.12;
15
15
  const offset = 3;
@@ -21,7 +21,7 @@ export function rendererCoordinatesRender(state, context, layer) {
21
21
  const label = geometry.orientation === 0 /* ColorCode.White */ ? String(8 - visualRank) : String(1 + visualRank);
22
22
  const r = geometry.getSquareRect(sq);
23
23
  const color = labelColorForSquare(sq, coords);
24
- const text = createSvgElement(layer, 'text', {
24
+ const text = createSvgElement(slot, 'text', {
25
25
  'data-chessboard-id': `coord-rank-${label}`,
26
26
  x: (r.x + offset).toString(),
27
27
  y: (r.y + offset).toString(),
@@ -44,7 +44,7 @@ export function rendererCoordinatesRender(state, context, layer) {
44
44
  : String.fromCharCode('h'.charCodeAt(0) - visualFile);
45
45
  const r = geometry.getSquareRect(sq);
46
46
  const color = labelColorForSquare(sq, coords);
47
- const text = createSvgElement(layer, 'text', {
47
+ const text = createSvgElement(slot, 'text', {
48
48
  'data-chessboard-id': `coord-file-${label}`,
49
49
  x: (r.x + r.width - offset).toString(),
50
50
  y: (r.y + r.height - offset).toString(),
@@ -4,5 +4,5 @@ export interface MainRendererCoordinatesInternal {
4
4
  readonly config: ConfigColorPair;
5
5
  }
6
6
  export interface MainRendererCoordinates {
7
- render(context: ExtensionRenderContext, layer: SVGElement): void;
7
+ render(context: ExtensionRenderContext, slot: SVGGElement): void;
8
8
  }
@@ -1,4 +1,4 @@
1
1
  import { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
2
- import { PieceUrls } from '../types/internal.js';
2
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
3
3
  import { MainRendererDrag } from './types.js';
4
- export declare function createMainRendererDrag(config: PieceUrls, runtimeSurface: ExtensionRuntimeSurface): MainRendererDrag;
4
+ export declare function createMainRendererDrag(runtimeSurface: ExtensionRuntimeSurface, resolver: PieceSymbolResolver): MainRendererDrag;
@@ -1,21 +1,24 @@
1
1
  import { rendererDragRenderTransientVisuals } from './render.js';
2
2
  import { rendererDragOnUpdate } from './update.js';
3
- function createMainRendererDragInternal(config, runtimeSurface) {
4
- return { config, runtimeSurface, isDragActive: false, pieceUrl: null, pieceNode: null };
5
- }
6
- export function createMainRendererDrag(config, runtimeSurface) {
7
- const internalState = createMainRendererDragInternal(config, runtimeSurface);
3
+ export function createMainRendererDrag(runtimeSurface, resolver) {
4
+ const internalState = {
5
+ runtimeSurface,
6
+ resolver,
7
+ isDragActive: false,
8
+ pieceCode: null,
9
+ pieceNode: null
10
+ };
8
11
  return {
9
12
  onUpdate(context) {
10
13
  rendererDragOnUpdate(internalState, context);
11
14
  },
12
- renderTransientVisuals(context, layer) {
13
- rendererDragRenderTransientVisuals(internalState, context, layer);
15
+ renderTransientVisuals(context, slot) {
16
+ rendererDragRenderTransientVisuals(internalState, context, slot);
14
17
  },
15
18
  unmount() {
16
19
  internalState.pieceNode?.remove();
17
20
  internalState.pieceNode = null;
18
- internalState.pieceUrl = null;
21
+ internalState.pieceCode = null;
19
22
  internalState.isDragActive = false;
20
23
  internalState.runtimeSurface.transientVisuals.unsubscribe();
21
24
  }
@@ -1,3 +1,3 @@
1
1
  import { ExtensionRenderTransientVisualsContext } from '../../../types/context/transient-visuals.js';
2
2
  import { MainRendererDragInternal } from './types.js';
3
- export declare function rendererDragRenderTransientVisuals(state: MainRendererDragInternal, context: ExtensionRenderTransientVisualsContext, layer: SVGElement): void;
3
+ export declare function rendererDragRenderTransientVisuals(state: MainRendererDragInternal, context: ExtensionRenderTransientVisualsContext, slot: SVGGElement): void;
@@ -1,23 +1,24 @@
1
- import { createSvgElement, updateElementAttributes } from '../../../../render/svg/helpers.js';
2
- export function rendererDragRenderTransientVisuals(state, context, layer) {
1
+ import { createSvgElement, updateSvgElementAttributes } from '../../../../render/svg/helpers.js';
2
+ export function rendererDragRenderTransientVisuals(state, context, slot) {
3
3
  const geometry = context.currentFrame.layout.geometry;
4
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();
8
- if (state.isDragActive && state.pieceUrl && !state.pieceNode) {
8
+ if (state.isDragActive && state.pieceCode && !state.pieceNode) {
9
9
  // Ok this is first render pass, let's create the piece node
10
- state.pieceNode = createSvgElement(layer, 'image', {
10
+ const href = state.resolver.getHref(state.pieceCode);
11
+ state.pieceNode = createSvgElement(slot, 'use', {
11
12
  'data-chessboard-id': 'dragged-piece',
12
13
  width: squareSize,
13
14
  height: squareSize,
14
15
  x,
15
16
  y,
16
- href: state.pieceUrl
17
+ href
17
18
  });
18
19
  }
19
20
  else if (state.isDragActive && state.pieceNode) {
20
- updateElementAttributes(state.pieceNode, {
21
+ updateSvgElementAttributes(state.pieceNode, {
21
22
  width: squareSize,
22
23
  height: squareSize,
23
24
  x,
@@ -1,16 +1,17 @@
1
+ import { NonEmptyPieceCode } from '../../../../state/board/types/internal.js';
1
2
  import { ExtensionRenderTransientVisualsContext } from '../../../types/context/transient-visuals.js';
2
3
  import { ExtensionUpdateContext } from '../../../types/context/update.js';
3
4
  import { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
4
- import { PieceUrls } from '../types/internal.js';
5
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
5
6
  export interface MainRendererDragInternal {
6
- readonly config: PieceUrls;
7
7
  readonly runtimeSurface: ExtensionRuntimeSurface;
8
+ readonly resolver: PieceSymbolResolver;
8
9
  isDragActive: boolean;
9
- pieceUrl: string | null;
10
- pieceNode: SVGImageElement | null;
10
+ pieceCode: NonEmptyPieceCode | null;
11
+ pieceNode: SVGUseElement | null;
11
12
  }
12
13
  export interface MainRendererDrag {
13
14
  onUpdate(context: ExtensionUpdateContext): void;
14
- renderTransientVisuals(context: ExtensionRenderTransientVisualsContext, layer: SVGElement): void;
15
+ renderTransientVisuals(context: ExtensionRenderTransientVisualsContext, slot: SVGGElement): void;
15
16
  unmount(): void;
16
17
  }
@@ -6,7 +6,7 @@ export function rendererDragOnUpdate(state, context) {
6
6
  if (!state.isDragActive) {
7
7
  const pieceCode = context.currentFrame.state.interaction.dragSession.sourcePieceCode;
8
8
  assert(isNonEmptyPieceCode(pieceCode), 'Invalid piece code in drag session');
9
- state.pieceUrl = state.config[pieceCode];
9
+ state.pieceCode = pieceCode;
10
10
  state.runtimeSurface.transientVisuals.subscribe();
11
11
  }
12
12
  state.isDragActive = true;
@@ -16,7 +16,7 @@ export function rendererDragOnUpdate(state, context) {
16
16
  state.runtimeSurface.transientVisuals.unsubscribe();
17
17
  state.pieceNode?.remove();
18
18
  state.pieceNode = null;
19
- state.pieceUrl = null;
19
+ state.pieceCode = null;
20
20
  }
21
21
  state.isDragActive = false;
22
22
  }
@@ -5,6 +5,7 @@ import { createMainRendererBoard } from './board/factory.js';
5
5
  import { createMainRendererCoordinates } from './coordinates/factory.js';
6
6
  import { createMainRendererDrag } from './drag/factory.js';
7
7
  import { normalizeMainRendererConfig } from './normalize.js';
8
+ import { createPieceSymbolResolver, ensurePieceSymbolsDefined } from './piece-symbols.js';
8
9
  import { createMainRendererPieces } from './pieces/factory.js';
9
10
  import { EXTENSION_ID, EXTENSION_SLOTS } from './types/extension.js';
10
11
  import { DEFAULT_MAIN_RENDERER_CONFIG } from './types/internal.js';
@@ -19,19 +20,21 @@ export function createMainRenderer(options = {}) {
19
20
  };
20
21
  }
21
22
  function createMainRendererInternal(options, config) {
23
+ const pieceSymbolResolver = createPieceSymbolResolver(options.svgIds);
22
24
  const board = createMainRendererBoard(config.colors.board);
23
25
  const coordinates = createMainRendererCoordinates(config.colors.coordinates);
24
- const pieces = createMainRendererPieces(config.pieceUrls);
25
- const drag = createMainRendererDrag(config.pieceUrls, options.runtimeSurface);
26
- const animation = createMainRendererAnimation(config.pieceUrls, options.runtimeSurface);
26
+ const pieces = createMainRendererPieces(pieceSymbolResolver);
27
+ const drag = createMainRendererDrag(options.runtimeSurface, pieceSymbolResolver);
28
+ const animation = createMainRendererAnimation(options.runtimeSurface, pieceSymbolResolver);
27
29
  return {
28
- ...extensionCreateInternalBase(),
30
+ ...extensionCreateInternalBase(options),
29
31
  board,
30
32
  coordinates,
31
33
  pieces,
32
34
  drag,
33
35
  animation,
34
- runtimeSurface: options.runtimeSurface
36
+ runtimeSurface: options.runtimeSurface,
37
+ pieceSymbolResolver
35
38
  };
36
39
  }
37
40
  function extensionUnmountLocal(state) {
@@ -52,6 +55,7 @@ function createMainRendererInstance(options, config) {
52
55
  id: EXTENSION_ID,
53
56
  mount(env) {
54
57
  extensionMountBase(internalState, env.slotRoots);
58
+ ensurePieceSymbolsDefined(env.slotRoots.defs, config.pieceUrls, internalState.pieceSymbolResolver);
55
59
  },
56
60
  onUpdate(context) {
57
61
  internalState.board.onUpdate(context);
@@ -0,0 +1,19 @@
1
+ import type { SvgIdResolver } from '../../../render/svg/ids.js';
2
+ import { NonEmptyPieceCode } from '../../../state/board/types/internal.js';
3
+ import type { PieceUrls } from './types/internal.js';
4
+ export interface PieceSymbolResolver {
5
+ /** Returns the DOM id for a piece symbol (e.g., "cb0-renderer-p6") */
6
+ getId(pieceCode: NonEmptyPieceCode): string;
7
+ /** Returns the href suitable for <use href="..."> (e.g., "#cb0-renderer-p6") */
8
+ getHref(pieceCode: NonEmptyPieceCode): string;
9
+ }
10
+ /**
11
+ * Creates a PieceSymbolResolver backed by the shared SvgIdResolver.
12
+ * All ids use the "renderer" scope with a "p{pieceCode}" local id.
13
+ */
14
+ export declare function createPieceSymbolResolver(svgIds: SvgIdResolver): PieceSymbolResolver;
15
+ /**
16
+ * Ensures all 12 piece symbols exist in the defs element.
17
+ * Idempotent: checks the actual DOM for each symbol id before creating.
18
+ */
19
+ export declare function ensurePieceSymbolsDefined(defs: SVGDefsElement, config: PieceUrls, resolver: PieceSymbolResolver): void;
@@ -0,0 +1,45 @@
1
+ import { createSvgElement } from '../../../render/svg/helpers.js';
2
+ import { ALL_NON_EMPTY_PIECE_CODES } from '../../../state/board/types/internal.js';
3
+ /**
4
+ * Creates a PieceSymbolResolver backed by the shared SvgIdResolver.
5
+ * All ids use the "renderer" scope with a "p{pieceCode}" local id.
6
+ */
7
+ export function createPieceSymbolResolver(svgIds) {
8
+ const getId = (pieceCode) => {
9
+ return svgIds.makeId('renderer', `p${pieceCode}`);
10
+ };
11
+ const getHref = (pieceCode) => {
12
+ return svgIds.makeHref('renderer', `p${pieceCode}`);
13
+ };
14
+ return { getId, getHref };
15
+ }
16
+ // ─── Symbol DOM creation ──────────────────────────────────────────────────────
17
+ function getSymbolDataId(pieceCode) {
18
+ return `piece-symbol-${pieceCode}`;
19
+ }
20
+ /**
21
+ * Ensures all 12 piece symbols exist in the defs element.
22
+ * Idempotent: checks the actual DOM for each symbol id before creating.
23
+ */
24
+ export function ensurePieceSymbolsDefined(defs, config, resolver) {
25
+ for (const pieceCode of ALL_NON_EMPTY_PIECE_CODES) {
26
+ const symbolId = resolver.getId(pieceCode);
27
+ // Idempotency: check if this symbol already exists in the DOM
28
+ if (defs.querySelector(`#${symbolId}`)) {
29
+ continue;
30
+ }
31
+ const symbol = createSvgElement(defs, 'symbol', {
32
+ id: symbolId,
33
+ 'data-chessboard-id': getSymbolDataId(pieceCode),
34
+ viewBox: '0 0 1 1'
35
+ });
36
+ createSvgElement(symbol, 'image', {
37
+ 'data-chessboard-id': `piece-symbol-image-${pieceCode}`,
38
+ href: config[pieceCode],
39
+ x: '0',
40
+ y: '0',
41
+ width: '1',
42
+ height: '1'
43
+ });
44
+ }
45
+ }
@@ -1,3 +1,3 @@
1
- import { PieceUrls } from '../types/internal.js';
1
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
2
2
  import { MainRendererPieces } from './types.js';
3
- export declare function createMainRendererPieces(config: PieceUrls): MainRendererPieces;
3
+ export declare function createMainRendererPieces(resolver: PieceSymbolResolver): MainRendererPieces;
@@ -1,8 +1,8 @@
1
1
  import { rendererPiecesRender } from './render.js';
2
2
  import { rendererPiecesOnUpdate, rendererPiecesRefreshSuppressedSquares } from './update.js';
3
- export function createMainRendererPieces(config) {
3
+ export function createMainRendererPieces(resolver) {
4
4
  const state = {
5
- config,
5
+ resolver,
6
6
  pieceNodes: new Map(),
7
7
  suppressedSquares: new Set()
8
8
  };
@@ -13,8 +13,8 @@ export function createMainRendererPieces(config) {
13
13
  refreshSuppressedSquares(context, animationSuppressedSquares) {
14
14
  rendererPiecesRefreshSuppressedSquares(state, context, animationSuppressedSquares);
15
15
  },
16
- render(context, layer) {
17
- rendererPiecesRender(state, context, layer);
16
+ render(context, slot) {
17
+ rendererPiecesRender(state, context, slot);
18
18
  },
19
19
  unmount() {
20
20
  for (const nodeRecord of state.pieceNodes.values()) {
@@ -1,3 +1,3 @@
1
1
  import { ExtensionRenderContext } from '../../../types/context/render.js';
2
2
  import { MainRendererPiecesInternal } from './types.js';
3
- export declare function rendererPiecesRender(state: MainRendererPiecesInternal, context: ExtensionRenderContext, layer: SVGElement): void;
3
+ export declare function rendererPiecesRender(state: MainRendererPiecesInternal, context: ExtensionRenderContext, slot: SVGGElement): void;
@@ -1,8 +1,8 @@
1
- import { createSvgElement, updateElementAttributes } from '../../../../render/svg/helpers.js';
1
+ import { createSvgElement, updateSvgElementAttributes } from '../../../../render/svg/helpers.js';
2
2
  import { isNonEmptyPieceCode } from '../../../../state/board/check.js';
3
3
  import { SQUARE_COUNT } from '../../../../state/board/types/internal.js';
4
4
  import { DirtyLayer } from '../types/extension.js';
5
- export function rendererPiecesRender(state, context, layer) {
5
+ export function rendererPiecesRender(state, context, slot) {
6
6
  if ((context.invalidation.dirtyLayers & DirtyLayer.Pieces) === 0) {
7
7
  return;
8
8
  }
@@ -13,22 +13,22 @@ export function rendererPiecesRender(state, context, layer) {
13
13
  const existing = state.pieceNodes.get(sq) || null;
14
14
  const suppressed = state.suppressedSquares.has(sq);
15
15
  if (isNonEmptyPieceCode(pieceCode) && !suppressed) {
16
- const url = state.config[pieceCode];
16
+ const href = state.resolver.getHref(pieceCode);
17
17
  const r = geometry.getSquareRect(sq);
18
18
  if (existing !== null) {
19
- updateElementAttributes(existing.root, {
19
+ updateSvgElementAttributes(existing.root, {
20
20
  x: r.x.toString(),
21
21
  y: r.y.toString(),
22
22
  width: r.width.toString(),
23
23
  height: r.height.toString(),
24
- href: url
24
+ href
25
25
  });
26
26
  }
27
27
  else {
28
28
  state.pieceNodes.set(sq, {
29
- root: createSvgElement(layer, 'image', {
29
+ root: createSvgElement(slot, 'use', {
30
30
  'data-chessboard-id': `piece-${pieceCode}-${sq}`,
31
- href: url,
31
+ href,
32
32
  x: r.x.toString(),
33
33
  y: r.y.toString(),
34
34
  width: r.width.toString(),
@@ -38,7 +38,7 @@ export function rendererPiecesRender(state, context, layer) {
38
38
  }
39
39
  }
40
40
  else if (existing !== null) {
41
- layer.removeChild(existing.root);
41
+ slot.removeChild(existing.root);
42
42
  state.pieceNodes.delete(sq);
43
43
  }
44
44
  }
@@ -2,19 +2,19 @@ import { Square } from '../../../../state/board/types/internal.js';
2
2
  import { ExtensionCleanAnimationContext } from '../../../types/context/animation.js';
3
3
  import { ExtensionRenderContext } from '../../../types/context/render.js';
4
4
  import { ExtensionUpdateContext } from '../../../types/context/update.js';
5
- import { PieceUrls } from '../types/internal.js';
5
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
6
6
  type PieceNodeRecord = {
7
- root: SVGImageElement;
7
+ root: SVGUseElement;
8
8
  };
9
9
  export interface MainRendererPiecesInternal {
10
- readonly config: PieceUrls;
10
+ readonly resolver: PieceSymbolResolver;
11
11
  readonly pieceNodes: Map<Square, PieceNodeRecord>;
12
12
  suppressedSquares: ReadonlySet<Square>;
13
13
  }
14
14
  export interface MainRendererPieces {
15
15
  onUpdate(context: ExtensionUpdateContext, animationSuppressedSquares: ReadonlySet<Square>): void;
16
16
  refreshSuppressedSquares(context: ExtensionCleanAnimationContext, animationSuppressedSquares: ReadonlySet<Square>): void;
17
- render(context: ExtensionRenderContext, layer: SVGElement): void;
17
+ render(context: ExtensionRenderContext, slot: SVGGElement): void;
18
18
  unmount(): void;
19
19
  }
20
20
  export {};
@@ -1,7 +1,7 @@
1
1
  import { ExtensionDefinition, ExtensionInstance } from '../../../types/extension.js';
2
2
  import { MainRendererConfigInput } from './input.js';
3
3
  export type MainRendererInitOptions = Partial<MainRendererConfigInput>;
4
- export declare const EXTENSION_SLOTS: readonly ["board", "coordinates", "pieces", "animation", "drag"];
4
+ export declare const EXTENSION_SLOTS: readonly ["defs", "board", "coordinates", "pieces", "animation", "drag"];
5
5
  export type ExtensionSlotsType = typeof EXTENSION_SLOTS;
6
6
  export declare const EXTENSION_ID: "renderer";
7
7
  export type MainRendererDefinition = ExtensionDefinition<typeof EXTENSION_ID, typeof EXTENSION_SLOTS, never>;
@@ -1,4 +1,11 @@
1
- export const EXTENSION_SLOTS = ['board', 'coordinates', 'pieces', 'animation', 'drag'];
1
+ export const EXTENSION_SLOTS = [
2
+ 'defs',
3
+ 'board',
4
+ 'coordinates',
5
+ 'pieces',
6
+ 'animation',
7
+ 'drag'
8
+ ];
2
9
  export const EXTENSION_ID = 'renderer';
3
10
  export var DirtyLayer;
4
11
  (function (DirtyLayer) {
@@ -4,6 +4,7 @@ import { MainRendererAnimation } from '../animation/types.js';
4
4
  import { MainRendererBoard } from '../board/types.js';
5
5
  import { MainRendererCoordinates } from '../coordinates/types.js';
6
6
  import { MainRendererDrag } from '../drag/types.js';
7
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
7
8
  import { MainRendererPieces } from '../pieces/types.js';
8
9
  import { ExtensionSlotsType } from './extension.js';
9
10
  export interface MainRendererInstanceInternal extends ExtensionInternalBase<ExtensionSlotsType> {
@@ -13,4 +14,5 @@ export interface MainRendererInstanceInternal extends ExtensionInternalBase<Exte
13
14
  readonly drag: MainRendererDrag;
14
15
  readonly animation: MainRendererAnimation;
15
16
  readonly runtimeSurface: ExtensionRuntimeSurface;
17
+ readonly pieceSymbolResolver: PieceSymbolResolver;
16
18
  }
@@ -1,5 +1,5 @@
1
1
  import assert from '@ktarmyshov/assert';
2
- import { createSvgElement, updateElementAttributes } from '../../../render/svg/helpers.js';
2
+ import { createSvgElement, updateSvgElementAttributes } from '../../../render/svg/helpers.js';
3
3
  import { fileOf, rankOf, squareOf } from '../../../state/board/coords.js';
4
4
  import { toPieceCode } from '../../../state/board/piece.js';
5
5
  import { isUpdateContextRenderable } from '../../types/context/update.js';
@@ -18,7 +18,7 @@ export function createPromotion(config = {}) {
18
18
  }
19
19
  function createPromotionInternal(options, config) {
20
20
  return {
21
- ...extensionCreateInternalBase(),
21
+ ...extensionCreateInternalBase(options),
22
22
  svgPromotionPieces: new Map(),
23
23
  activePromotionSquares: new Map(),
24
24
  svgHoverRect: null,
@@ -117,7 +117,7 @@ function createPromotionInstance(options, config) {
117
117
  }
118
118
  // Create or update a node for each promotion role
119
119
  assert(internalState.slotRoots, 'Slot roots should be available when render is called');
120
- const layer = internalState.slotRoots.animation;
120
+ const slot = internalState.slotRoots.animation;
121
121
  for (let i = 0; i < promotedToSorted.length; i++) {
122
122
  const roleCode = promotedToSorted[i];
123
123
  const displayRank = (targetRank + i * rankStep);
@@ -144,15 +144,15 @@ function createPromotionInstance(options, config) {
144
144
  internalState.activePromotionSquares.set(displaySquare, roleCode);
145
145
  const existing = internalState.svgPromotionPieces.get(roleCode) ?? null;
146
146
  if (existing !== null) {
147
- updateElementAttributes(existing.rect, rectAttrs);
148
- updateElementAttributes(existing.svg, imageAttrs);
147
+ updateSvgElementAttributes(existing.rect, rectAttrs);
148
+ updateSvgElementAttributes(existing.svg, imageAttrs);
149
149
  }
150
150
  else {
151
- const rect = createSvgElement(layer, 'rect', {
151
+ const rect = createSvgElement(slot, 'rect', {
152
152
  'data-chessboard-id': `promotion-bg-${roleCode}`,
153
153
  ...rectAttrs
154
154
  });
155
- const svg = createSvgElement(layer, 'image', {
155
+ const svg = createSvgElement(slot, 'image', {
156
156
  'data-chessboard-id': `promotion-piece-${roleCode}`,
157
157
  ...imageAttrs
158
158
  });
@@ -176,12 +176,12 @@ function createPromotionInstance(options, config) {
176
176
  'shape-rendering': 'crispEdges'
177
177
  };
178
178
  if (internalState.svgHoverRect) {
179
- updateElementAttributes(internalState.svgHoverRect, attrs);
179
+ updateSvgElementAttributes(internalState.svgHoverRect, attrs);
180
180
  }
181
181
  else {
182
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', {
183
+ const slot = internalState.slotRoots.animation;
184
+ internalState.svgHoverRect = createSvgElement(slot, 'rect', {
185
185
  'data-chessboard-id': 'promotion-hover',
186
186
  ...attrs
187
187
  });
@@ -1,6 +1,6 @@
1
1
  import assert from '@ktarmyshov/assert';
2
2
  import { toMerged } from 'es-toolkit';
3
- import { createSvgElement, updateElementAttributes } from '../../../render/svg/helpers.js';
3
+ import { createSvgElement, updateSvgElementAttributes } from '../../../render/svg/helpers.js';
4
4
  import { isUpdateContextRenderable } from '../../types/context/update.js';
5
5
  import { extensionCreateInternalBase, extensionDestroyBase, extensionMountBase, extensionUnmountBase } from '../common/helpers.js';
6
6
  import { DEFAULT_CONFIG, EXTENSION_ID, EXTENSION_SLOTS } from './types.js';
@@ -9,14 +9,14 @@ export function createSelectedSquare(config = {}) {
9
9
  return {
10
10
  id: EXTENSION_ID,
11
11
  slots: EXTENSION_SLOTS,
12
- createInstance() {
13
- return createSelectedSquareInstance(mergedConfig);
12
+ createInstance(options) {
13
+ return createSelectedSquareInstance(options, mergedConfig);
14
14
  }
15
15
  };
16
16
  }
17
- function createSelectedSquareInternal(config) {
17
+ function createSelectedSquareInternal(options, config) {
18
18
  return {
19
- ...extensionCreateInternalBase(),
19
+ ...extensionCreateInternalBase(options),
20
20
  svgRect: null,
21
21
  config
22
22
  };
@@ -24,8 +24,8 @@ function createSelectedSquareInternal(config) {
24
24
  function extensionClean(state) {
25
25
  state.svgRect = null;
26
26
  }
27
- function createSelectedSquareInstance(config) {
28
- const internalState = createSelectedSquareInternal(config);
27
+ function createSelectedSquareInstance(options, config) {
28
+ const internalState = createSelectedSquareInternal(options, config);
29
29
  return {
30
30
  id: EXTENSION_ID,
31
31
  mount(env) {
@@ -73,7 +73,7 @@ function createSelectedSquareInstance(config) {
73
73
  });
74
74
  }
75
75
  else {
76
- updateElementAttributes(internalState.svgRect, rectAttributes);
76
+ updateSvgElementAttributes(internalState.svgRect, rectAttributes);
77
77
  }
78
78
  },
79
79
  unmount() {
@@ -0,0 +1,2 @@
1
+ import { WatermarkDefinition } from './types.js';
2
+ export declare function createWatermark(): WatermarkDefinition;