@mirasen/chessboard 1.1.5 → 1.2.1

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 (131) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +61 -13
  3. package/dist/animation/render/fade.d.ts +2 -3
  4. package/dist/animation/render/fade.js +6 -6
  5. package/dist/animation/render/move.d.ts +2 -3
  6. package/dist/animation/render/move.js +6 -6
  7. package/dist/animation/render/plan.d.ts +3 -4
  8. package/dist/animation/render/plan.js +5 -5
  9. package/dist/animation/render/static.d.ts +2 -3
  10. package/dist/animation/render/static.js +4 -4
  11. package/dist/animation/render/types.d.ts +10 -4
  12. package/dist/extensions/build/index.d.ts +1 -2
  13. package/dist/extensions/build/index.js +1 -1
  14. package/dist/extensions/factory/main.js +11 -7
  15. package/dist/extensions/factory/runtime.js +24 -1
  16. package/dist/extensions/first-party/active-target/factory.js +18 -11
  17. package/dist/extensions/first-party/annotations/api.d.ts +16 -0
  18. package/dist/extensions/first-party/annotations/api.js +79 -0
  19. package/dist/extensions/first-party/annotations/committed.d.ts +3 -0
  20. package/dist/extensions/first-party/annotations/committed.js +11 -0
  21. package/dist/extensions/first-party/annotations/constants.d.ts +3 -0
  22. package/dist/extensions/first-party/annotations/constants.js +54 -0
  23. package/dist/extensions/first-party/annotations/denormalize.d.ts +4 -0
  24. package/dist/extensions/first-party/annotations/denormalize.js +22 -0
  25. package/dist/extensions/first-party/annotations/factory.d.ts +3 -0
  26. package/dist/extensions/first-party/annotations/factory.js +143 -0
  27. package/dist/extensions/first-party/annotations/interaction.d.ts +8 -0
  28. package/dist/extensions/first-party/annotations/interaction.js +129 -0
  29. package/dist/extensions/first-party/annotations/invalidation.d.ts +3 -0
  30. package/dist/extensions/first-party/annotations/invalidation.js +7 -0
  31. package/dist/extensions/first-party/annotations/normalize.d.ts +12 -0
  32. package/dist/extensions/first-party/annotations/normalize.js +54 -0
  33. package/dist/extensions/first-party/annotations/render/committed.d.ts +3 -0
  34. package/dist/extensions/first-party/annotations/render/committed.js +123 -0
  35. package/dist/extensions/first-party/annotations/render/helpers/arrows.d.ts +33 -0
  36. package/dist/extensions/first-party/annotations/render/helpers/arrows.js +93 -0
  37. package/dist/extensions/first-party/annotations/render/helpers/circles.d.ts +18 -0
  38. package/dist/extensions/first-party/annotations/render/helpers/circles.js +30 -0
  39. package/dist/extensions/first-party/annotations/render/preview.d.ts +3 -0
  40. package/dist/extensions/first-party/annotations/render/preview.js +96 -0
  41. package/dist/extensions/first-party/annotations/types/internal.d.ts +77 -0
  42. package/dist/extensions/first-party/annotations/types/internal.js +1 -0
  43. package/dist/extensions/first-party/annotations/types/main.d.ts +40 -0
  44. package/dist/extensions/first-party/annotations/types/main.js +6 -0
  45. package/dist/extensions/first-party/annotations/types/public.d.ts +48 -0
  46. package/dist/extensions/first-party/annotations/types/public.js +1 -0
  47. package/dist/extensions/first-party/annotations/update.d.ts +3 -0
  48. package/dist/extensions/first-party/annotations/update.js +45 -0
  49. package/dist/extensions/first-party/auto-promote/factory.js +6 -6
  50. package/dist/extensions/first-party/board-events/factory.js +6 -6
  51. package/dist/extensions/first-party/common/helpers.d.ts +2 -1
  52. package/dist/extensions/first-party/common/helpers.js +6 -5
  53. package/dist/extensions/first-party/common/types.d.ts +2 -0
  54. package/dist/extensions/first-party/last-move/factory.js +9 -9
  55. package/dist/extensions/first-party/legal-moves/factory.js +8 -8
  56. package/dist/extensions/first-party/main-renderer/animation/factory.d.ts +2 -2
  57. package/dist/extensions/first-party/main-renderer/animation/factory.js +4 -4
  58. package/dist/extensions/first-party/main-renderer/animation/render.d.ts +1 -1
  59. package/dist/extensions/first-party/main-renderer/animation/render.js +2 -2
  60. package/dist/extensions/first-party/main-renderer/animation/types.d.ts +3 -3
  61. package/dist/extensions/first-party/main-renderer/board/factory.js +2 -2
  62. package/dist/extensions/first-party/main-renderer/board/render.d.ts +1 -1
  63. package/dist/extensions/first-party/main-renderer/board/render.js +4 -4
  64. package/dist/extensions/first-party/main-renderer/board/types.d.ts +1 -1
  65. package/dist/extensions/first-party/main-renderer/coordinates/factory.js +2 -2
  66. package/dist/extensions/first-party/main-renderer/coordinates/render.d.ts +1 -1
  67. package/dist/extensions/first-party/main-renderer/coordinates/render.js +5 -5
  68. package/dist/extensions/first-party/main-renderer/coordinates/types.d.ts +1 -1
  69. package/dist/extensions/first-party/main-renderer/drag/factory.d.ts +2 -2
  70. package/dist/extensions/first-party/main-renderer/drag/factory.js +11 -8
  71. package/dist/extensions/first-party/main-renderer/drag/render.d.ts +1 -1
  72. package/dist/extensions/first-party/main-renderer/drag/render.js +7 -6
  73. package/dist/extensions/first-party/main-renderer/drag/types.d.ts +6 -5
  74. package/dist/extensions/first-party/main-renderer/drag/update.js +2 -2
  75. package/dist/extensions/first-party/main-renderer/factory.js +9 -5
  76. package/dist/extensions/first-party/main-renderer/piece-symbols.d.ts +19 -0
  77. package/dist/extensions/first-party/main-renderer/piece-symbols.js +45 -0
  78. package/dist/extensions/first-party/main-renderer/pieces/factory.d.ts +2 -2
  79. package/dist/extensions/first-party/main-renderer/pieces/factory.js +4 -4
  80. package/dist/extensions/first-party/main-renderer/pieces/render.d.ts +1 -1
  81. package/dist/extensions/first-party/main-renderer/pieces/render.js +8 -8
  82. package/dist/extensions/first-party/main-renderer/pieces/types.d.ts +4 -4
  83. package/dist/extensions/first-party/main-renderer/types/extension.d.ts +1 -1
  84. package/dist/extensions/first-party/main-renderer/types/extension.js +8 -1
  85. package/dist/extensions/first-party/main-renderer/types/instance.d.ts +2 -0
  86. package/dist/extensions/first-party/promotion/factory.js +10 -10
  87. package/dist/extensions/first-party/selected-square/factory.js +8 -8
  88. package/dist/extensions/first-party/watermark/factory.js +8 -8
  89. package/dist/extensions/types/basic/events.d.ts +32 -3
  90. package/dist/extensions/types/basic/events.js +2 -1
  91. package/dist/extensions/types/basic/mount.d.ts +5 -2
  92. package/dist/extensions/types/basic/mount.js +0 -2
  93. package/dist/extensions/types/basic/transient-visuals.d.ts +1 -1
  94. package/dist/extensions/types/context/animation.d.ts +3 -3
  95. package/dist/extensions/types/context/events.d.ts +2 -1
  96. package/dist/extensions/types/context/update.d.ts +6 -7
  97. package/dist/extensions/types/extension.d.ts +3 -0
  98. package/dist/extensions/types/main.d.ts +1 -0
  99. package/dist/extensions/types/surface/commands.d.ts +1 -1
  100. package/dist/extensions/types/surface/main.d.ts +6 -4
  101. package/dist/extensions/types/wrapper.d.ts +3 -1
  102. package/dist/extensions/types/wrapper.js +6 -2
  103. package/dist/extensions/update.js +6 -10
  104. package/dist/index.d.ts +1 -1
  105. package/dist/render/mount.js +3 -3
  106. package/dist/render/rendering/state.js +2 -2
  107. package/dist/render/svg/factory.js +40 -25
  108. package/dist/render/svg/helpers.d.ts +8 -4
  109. package/dist/render/svg/helpers.js +27 -12
  110. package/dist/render/svg/ids.d.ts +6 -0
  111. package/dist/render/svg/ids.js +11 -0
  112. package/dist/render/types.d.ts +4 -3
  113. package/dist/render/types.js +2 -1
  114. package/dist/runtime/factory/input.d.ts +3 -1
  115. package/dist/runtime/factory/input.js +11 -2
  116. package/dist/runtime/factory/main.js +10 -4
  117. package/dist/runtime/input/adapter/event.js +2 -2
  118. package/dist/runtime/input/adapter/pointer.d.ts +2 -2
  119. package/dist/runtime/input/controller/factory.d.ts +1 -1
  120. package/dist/runtime/input/controller/factory.js +35 -15
  121. package/dist/runtime/input/controller/helpers.d.ts +1 -1
  122. package/dist/runtime/input/controller/interaction.d.ts +3 -0
  123. package/dist/runtime/input/controller/interaction.js +14 -0
  124. package/dist/runtime/input/controller/pointer.d.ts +6 -6
  125. package/dist/runtime/input/controller/pointer.js +53 -27
  126. package/dist/runtime/input/controller/transient-visuals.d.ts +2 -3
  127. package/dist/runtime/input/controller/types.d.ts +6 -5
  128. package/dist/runtime/types/main.d.ts +1 -1
  129. package/dist/wrapper/factory.js +2 -7
  130. package/dist/wrapper/types.d.ts +3 -11
  131. package/package.json +1 -2
@@ -5,14 +5,14 @@ export function createBoardEvents() {
5
5
  return {
6
6
  id: EXTENSION_ID,
7
7
  slots: EXTENSION_SLOTS,
8
- createInstance() {
9
- return createBoardEventsInstance();
8
+ createInstance(options) {
9
+ return createBoardEventsInstance(options);
10
10
  }
11
11
  };
12
12
  }
13
- function createBoardEventsInternal() {
13
+ function createBoardEventsInternal(options) {
14
14
  return {
15
- ...extensionCreateInternalBase(),
15
+ ...extensionCreateInternalBase(options),
16
16
  onUIMove: null,
17
17
  onRawUpdate: null
18
18
  };
@@ -27,8 +27,8 @@ function createBoardEventsInstancePublic(state) {
27
27
  }
28
28
  };
29
29
  }
30
- function createBoardEventsInstance() {
31
- const internalState = createBoardEventsInternal();
30
+ function createBoardEventsInstance(options) {
31
+ const internalState = createBoardEventsInternal(options);
32
32
  const publicInterface = createBoardEventsInstancePublic(internalState);
33
33
  return {
34
34
  id: EXTENSION_ID,
@@ -1,6 +1,7 @@
1
1
  import { ExtensionSlotName } from '../../types/basic/mount.js';
2
+ import type { ExtensionCreateInstanceOptions } from '../../types/extension.js';
2
3
  import { ExtensionInternalBase } from './types.js';
3
- export declare function extensionCreateInternalBase<TSlots extends readonly ExtensionSlotName[]>(): ExtensionInternalBase<TSlots>;
4
+ export declare function extensionCreateInternalBase<TSlots extends readonly ExtensionSlotName[]>(options: ExtensionCreateInstanceOptions): ExtensionInternalBase<TSlots>;
4
5
  export declare function extensionIsMountedBase<TSlots extends readonly ExtensionSlotName[]>(state: ExtensionInternalBase<TSlots>): boolean;
5
6
  export declare function extensionIsDestroyedBase<TSlots extends readonly ExtensionSlotName[]>(state: ExtensionInternalBase<TSlots>): boolean;
6
7
  export declare function extensionMountBase<TSlots extends readonly ExtensionSlotName[]>(state: ExtensionInternalBase<TSlots>, slotRoots: NonNullable<ExtensionInternalBase<TSlots>['slotRoots']>): void;
@@ -1,9 +1,10 @@
1
1
  import assert from '@ktarmyshov/assert';
2
- import { clearElementChildren } from '../../../render/svg/helpers.js';
3
- export function extensionCreateInternalBase() {
2
+ import { clearSvgElementChildren } from '../../../render/svg/helpers.js';
3
+ export function extensionCreateInternalBase(options) {
4
4
  return {
5
5
  slotRoots: null,
6
- destroyed: false
6
+ destroyed: false,
7
+ svgIds: options.svgIds
7
8
  };
8
9
  }
9
10
  export function extensionIsMountedBase(state) {
@@ -19,8 +20,8 @@ export function extensionMountBase(state, slotRoots) {
19
20
  }
20
21
  export function extensionUnmountBase(state) {
21
22
  assert(extensionIsMountedBase(state), 'Extension is not mounted');
22
- for (const slotRoot of Object.values(state.slotRoots ?? {})) {
23
- clearElementChildren(slotRoot);
23
+ for (const [, slotRoot] of Object.entries(state.slotRoots ?? {})) {
24
+ clearSvgElementChildren(slotRoot);
24
25
  }
25
26
  state.slotRoots = null;
26
27
  }
@@ -1,7 +1,9 @@
1
+ import type { SvgIdResolver } from '../../../render/svg/ids.js';
1
2
  import { ExtensionSlotName, ExtensionSlotSvgRoots } from '../../types/basic/mount.js';
2
3
  export interface ExtensionInternalBase<TSlots extends readonly ExtensionSlotName[]> {
3
4
  slotRoots: ExtensionSlotSvgRoots<TSlots> | null;
4
5
  destroyed: boolean;
6
+ readonly svgIds: SvgIdResolver;
5
7
  }
6
8
  export interface OpaqueColor {
7
9
  color: string;
@@ -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 createLastMove(config = {}) {
9
9
  return {
10
10
  id: EXTENSION_ID,
11
11
  slots: EXTENSION_SLOTS,
12
- createInstance() {
13
- return createLastMoveInstance(mergedConfig);
12
+ createInstance(options) {
13
+ return createLastMoveInstance(options, mergedConfig);
14
14
  }
15
15
  };
16
16
  }
17
- function createLastMoveInternal(config) {
17
+ function createLastMoveInternal(options, config) {
18
18
  return {
19
- ...extensionCreateInternalBase(),
19
+ ...extensionCreateInternalBase(options),
20
20
  svgRectFrom: null,
21
21
  svgRectTo: null,
22
22
  config
@@ -26,8 +26,8 @@ function extensionClean(state) {
26
26
  state.svgRectFrom = null;
27
27
  state.svgRectTo = null;
28
28
  }
29
- function createLastMoveInstance(config) {
30
- const internalState = createLastMoveInternal(config);
29
+ function createLastMoveInstance(options, config) {
30
+ const internalState = createLastMoveInternal(options, config);
31
31
  return {
32
32
  id: EXTENSION_ID,
33
33
  mount(env) {
@@ -89,9 +89,9 @@ function createLastMoveInstance(config) {
89
89
  });
90
90
  }
91
91
  else {
92
- updateElementAttributes(internalState.svgRectFrom, rectFromAttributes);
92
+ updateSvgElementAttributes(internalState.svgRectFrom, rectFromAttributes);
93
93
  assert(internalState.svgRectTo, 'svgRectTo should be available if svgRectFrom is available');
94
- updateElementAttributes(internalState.svgRectTo, rectToAttributes);
94
+ updateSvgElementAttributes(internalState.svgRectTo, rectToAttributes);
95
95
  }
96
96
  },
97
97
  unmount() {
@@ -1,6 +1,6 @@
1
1
  import assert from '@ktarmyshov/assert';
2
2
  import { toMerged } from 'es-toolkit';
3
- import { clearElementChildren, createSvgElement } from '../../../render/svg/helpers.js';
3
+ import { clearSvgElementChildren, createSvgElement } from '../../../render/svg/helpers.js';
4
4
  import { isNonEmptyPieceCode } from '../../../state/board/check.js';
5
5
  import { fromPieceCode } from '../../../state/board/piece.js';
6
6
  import { isUpdateContextRenderable } from '../../types/context/update.js';
@@ -11,14 +11,14 @@ export function createLegalMoves(config = {}) {
11
11
  return {
12
12
  id: EXTENSION_ID,
13
13
  slots: EXTENSION_SLOTS,
14
- createInstance() {
15
- return createLegalMovesInstance(mergedConfig);
14
+ createInstance(options) {
15
+ return createLegalMovesInstance(options, mergedConfig);
16
16
  }
17
17
  };
18
18
  }
19
- function createLegalMovesInternal(config) {
19
+ function createLegalMovesInternal(options, config) {
20
20
  return {
21
- ...extensionCreateInternalBase(),
21
+ ...extensionCreateInternalBase(options),
22
22
  svgCircles: [],
23
23
  config
24
24
  };
@@ -26,8 +26,8 @@ function createLegalMovesInternal(config) {
26
26
  function extensionClean(state) {
27
27
  state.svgCircles = [];
28
28
  }
29
- function createLegalMovesInstance(config) {
30
- const internalState = createLegalMovesInternal(config);
29
+ function createLegalMovesInstance(options, config) {
30
+ const internalState = createLegalMovesInternal(options, config);
31
31
  return {
32
32
  id: EXTENSION_ID,
33
33
  mount(env) {
@@ -47,7 +47,7 @@ function createLegalMovesInstance(config) {
47
47
  render(context) {
48
48
  assert(context.invalidation.dirtyLayers !== 0, 'Render should only be called when there are dirty layers');
49
49
  assert(internalState.slotRoots, 'Slot roots should be available when render is called');
50
- clearElementChildren(internalState.slotRoots.overPieces);
50
+ clearSvgElementChildren(internalState.slotRoots.overPieces);
51
51
  internalState.svgCircles = [];
52
52
  const interaction = context.currentFrame.state.interaction;
53
53
  const needsRender = interaction.movability.mode === 2 /* MovabilityModeCode.Strict */ &&
@@ -1,4 +1,4 @@
1
1
  import type { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
2
- import type { PieceUrls } from '../types/internal.js';
2
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
3
3
  import type { MainRendererAnimation } from './types.js';
4
- export declare function createMainRendererAnimation(config: PieceUrls, runtimeSurface: ExtensionRuntimeSurface): MainRendererAnimation;
4
+ export declare function createMainRendererAnimation(runtimeSurface: ExtensionRuntimeSurface, resolver: PieceSymbolResolver): MainRendererAnimation;
@@ -1,18 +1,18 @@
1
1
  import { cleanAnimationPlan } from '../../../../animation/render/plan.js';
2
2
  import { getAnimationSuppressedSquares, rendererAnimationClean, rendererAnimationPrepare, rendererAnimationRender } from './render.js';
3
3
  import { rendererAnimationOnUpdate } from './update.js';
4
- export function createMainRendererAnimation(config, runtimeSurface) {
4
+ export function createMainRendererAnimation(runtimeSurface, resolver) {
5
5
  const state = {
6
- config,
7
6
  runtimeSurface,
7
+ resolver,
8
8
  entries: new Map()
9
9
  };
10
10
  return {
11
11
  onUpdate(context) {
12
12
  rendererAnimationOnUpdate(state, context);
13
13
  },
14
- prepareAnimation(context, layer) {
15
- rendererAnimationPrepare(state, context, layer);
14
+ prepareAnimation(context, slot) {
15
+ rendererAnimationPrepare(state, context, slot);
16
16
  },
17
17
  renderAnimation(context) {
18
18
  rendererAnimationRender(state, context);
@@ -1,7 +1,7 @@
1
1
  import type { Square } from '../../../../state/board/types/internal.js';
2
2
  import type { ExtensionCleanAnimationContext, ExtensionPrepareAnimationContext, ExtensionRenderAnimationContext } from '../../../types/context/animation.js';
3
3
  import type { MainRendererAnimationInternal } from './types.js';
4
- export declare function rendererAnimationPrepare(state: MainRendererAnimationInternal, context: ExtensionPrepareAnimationContext, layer: SVGElement): void;
4
+ export declare function rendererAnimationPrepare(state: MainRendererAnimationInternal, context: ExtensionPrepareAnimationContext, slot: SVGGElement): void;
5
5
  export declare function rendererAnimationRender(state: MainRendererAnimationInternal, context: ExtensionRenderAnimationContext): void;
6
6
  export declare function rendererAnimationClean(state: MainRendererAnimationInternal, context: ExtensionCleanAnimationContext): void;
7
7
  export declare function getAnimationSuppressedSquares(state: MainRendererAnimationInternal): ReadonlySet<Square>;
@@ -1,10 +1,10 @@
1
1
  import { cleanAnimationPlan, prepareAnimationPlan, renderAnimationPlan } from '../../../../animation/render/plan.js';
2
2
  const EMPTY_SQUARES = new Set();
3
- export function rendererAnimationPrepare(state, context, layer) {
3
+ export function rendererAnimationPrepare(state, context, slot) {
4
4
  for (const session of context.submittedSessions) {
5
5
  const entry = state.entries.get(session.id);
6
6
  if (entry && entry.nodes === null) {
7
- entry.nodes = prepareAnimationPlan(entry.plan, context.currentFrame.layout.geometry, state.config, layer);
7
+ entry.nodes = prepareAnimationPlan(entry.plan, context.currentFrame.layout.geometry, state.resolver.getHref, slot);
8
8
  }
9
9
  }
10
10
  }
@@ -4,19 +4,19 @@ import { Square } from '../../../../state/board/types/internal.js';
4
4
  import { ExtensionCleanAnimationContext, ExtensionPrepareAnimationContext, ExtensionRenderAnimationContext } from '../../../types/context/animation.js';
5
5
  import { ExtensionUpdateContext } from '../../../types/context/update.js';
6
6
  import { ExtensionRuntimeSurface } from '../../../types/surface/main.js';
7
- import type { PieceUrls } from '../types/internal.js';
7
+ import type { PieceSymbolResolver } from '../piece-symbols.js';
8
8
  export interface MainRendererAnimationEntry {
9
9
  plan: AnimationPlan;
10
10
  nodes: PreparedNodeMap | null;
11
11
  }
12
12
  export interface MainRendererAnimationInternal {
13
- readonly config: PieceUrls;
14
13
  readonly runtimeSurface: ExtensionRuntimeSurface;
14
+ readonly resolver: PieceSymbolResolver;
15
15
  readonly entries: Map<number, MainRendererAnimationEntry>;
16
16
  }
17
17
  export interface MainRendererAnimation {
18
18
  onUpdate(context: ExtensionUpdateContext): void;
19
- prepareAnimation(context: ExtensionPrepareAnimationContext, layer: SVGElement): void;
19
+ prepareAnimation(context: ExtensionPrepareAnimationContext, slot: SVGGElement): void;
20
20
  renderAnimation(context: ExtensionRenderAnimationContext): void;
21
21
  cleanAnimation(context: ExtensionCleanAnimationContext): void;
22
22
  getSuppressedSquares(): ReadonlySet<Square>;
@@ -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()) {