@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
@@ -1,43 +1,58 @@
1
- import { createSvgElement } from './helpers.js';
1
+ import { createSvgDefsElement, createSvgElement, createSvgRootElement } from './helpers.js';
2
2
  export function createSvgRoots(options) {
3
- const { doc } = options;
4
- const svgRoot = createSvgElement(doc, 'svg', { 'data-chessboard-id': 'svg-root' });
3
+ const { element } = options;
4
+ const svgRoot = createSvgRootElement(element, { 'data-chessboard-id': 'svg-root' });
5
5
  svgRoot.style.setProperty('user-select', 'none');
6
6
  svgRoot.style.setProperty('-webkit-user-select', 'none');
7
7
  svgRoot.style.setProperty('touch-action', 'pinch-zoom');
8
8
  svgRoot.style.setProperty('-webkit-touch-callout', 'none');
9
9
  svgRoot.style.setProperty('-webkit-tap-highlight-color', 'transparent');
10
+ // Create children in the correct order
11
+ const board = createSvgElement(svgRoot, 'g', { 'data-chessboard-id': 'board-root' });
12
+ const coordinates = createSvgElement(svgRoot, 'g', {
13
+ 'data-chessboard-id': 'coordinates-root'
14
+ });
15
+ const underPieces = createSvgElement(svgRoot, 'g', {
16
+ 'data-chessboard-id': 'under-pieces-root'
17
+ });
18
+ const pieces = createSvgElement(svgRoot, 'g', { 'data-chessboard-id': 'pieces-root' });
19
+ const overPieces = createSvgElement(svgRoot, 'g', {
20
+ 'data-chessboard-id': 'over-pieces-root'
21
+ });
22
+ const animation = createSvgElement(svgRoot, 'g', {
23
+ 'data-chessboard-id': 'animation-root'
24
+ });
25
+ const underDrag = createSvgElement(svgRoot, 'g', {
26
+ 'data-chessboard-id': 'under-drag-root'
27
+ });
28
+ const drag = createSvgElement(svgRoot, 'g', { 'data-chessboard-id': 'drag-root' });
29
+ const overDrag = createSvgElement(svgRoot, 'g', {
30
+ 'data-chessboard-id': 'over-drag-root'
31
+ });
10
32
  const result = {
11
33
  svgRoot,
12
- defs: createSvgElement(doc, 'defs', { 'data-chessboard-id': 'defs-root' }),
13
- board: createSvgElement(doc, 'g', { 'data-chessboard-id': 'board-root' }),
14
- coordinates: createSvgElement(doc, 'g', { 'data-chessboard-id': 'coordinates-root' }),
15
- underPieces: createSvgElement(doc, 'g', { 'data-chessboard-id': 'under-pieces-root' }),
16
- pieces: createSvgElement(doc, 'g', { 'data-chessboard-id': 'pieces-root' }),
17
- overPieces: createSvgElement(doc, 'g', { 'data-chessboard-id': 'over-pieces-root' }),
18
- animation: createSvgElement(doc, 'g', { 'data-chessboard-id': 'animation-root' }),
19
- underDrag: createSvgElement(doc, 'g', { 'data-chessboard-id': 'under-drag-root' }),
20
- drag: createSvgElement(doc, 'g', { 'data-chessboard-id': 'drag-root' }),
21
- overDrag: createSvgElement(doc, 'g', { 'data-chessboard-id': 'over-drag-root' })
34
+ board,
35
+ coordinates,
36
+ underPieces,
37
+ pieces,
38
+ overPieces,
39
+ animation,
40
+ underDrag,
41
+ drag,
42
+ overDrag
22
43
  };
23
- // Attach layers in the correct order
24
- svgRoot.appendChild(result.defs);
25
- svgRoot.appendChild(result.board);
26
- svgRoot.appendChild(result.coordinates);
27
- svgRoot.appendChild(result.underPieces);
28
- svgRoot.appendChild(result.pieces);
29
- svgRoot.appendChild(result.overPieces);
30
- svgRoot.appendChild(result.animation);
31
- svgRoot.appendChild(result.underDrag);
32
- svgRoot.appendChild(result.drag);
33
- svgRoot.appendChild(result.overDrag);
34
44
  return result;
35
45
  }
36
46
  export function allocateExtensionSlotRoots(svgRoots, extensionId, slots) {
37
47
  const result = {};
38
48
  for (const slot of slots) {
39
49
  const id = `extension-slot-root-${slot}-${extensionId}`;
40
- result[slot] = createSvgElement(svgRoots[slot], 'g', { 'data-chessboard-id': id });
50
+ if (slot === 'defs') {
51
+ result[slot] = createSvgDefsElement(svgRoots.svgRoot, { 'data-chessboard-id': id });
52
+ }
53
+ else {
54
+ result[slot] = createSvgElement(svgRoots[slot], 'g', { 'data-chessboard-id': id });
55
+ }
41
56
  }
42
57
  return result;
43
58
  }
@@ -9,10 +9,14 @@ export declare const SVG_NS = "http://www.w3.org/2000/svg";
9
9
  export declare function isLightSquare(sq: Square): boolean;
10
10
  export type SvgElementNames = keyof SVGElementTagNameMap;
11
11
  export type SvgElementAttributes = Record<string, string>;
12
- export type SvgElementOtherAttributes = SvgElementAttributes;
13
12
  export type SvgElementWithIdAttributes = SvgElementAttributes & {
14
13
  'data-chessboard-id': string;
15
14
  };
16
- export declare function createSvgElement<K extends SvgElementNames>(parentOrDoc: Element | Document, name: K, attrs: SvgElementWithIdAttributes): SVGElementTagNameMap[K];
17
- export declare function updateElementAttributes(element: Element, attrs: SvgElementOtherAttributes): void;
18
- export declare function clearElementChildren(element: Element): void;
15
+ export type SvgElementOtherAttributes = SvgElementAttributes & {
16
+ 'data-chessboard-id'?: never;
17
+ };
18
+ export declare function createSvgRootElement(parent: HTMLElement, attrs: SvgElementWithIdAttributes): SVGSVGElement;
19
+ export declare function createSvgDefsElement(parent: SVGSVGElement, attrs: SvgElementWithIdAttributes): SVGDefsElement;
20
+ export declare function createSvgElement<K extends Exclude<SvgElementNames, 'svg' | 'defs'>>(parent: SVGElement, name: K, attrs: SvgElementWithIdAttributes): SVGElementTagNameMap[K];
21
+ export declare function updateSvgElementAttributes(element: SVGElement, attrs: SvgElementOtherAttributes): void;
22
+ export declare function clearSvgElementChildren(element: SVGElement): void;
@@ -11,23 +11,38 @@ export function isLightSquare(sq) {
11
11
  const r = rankOf(sq);
12
12
  return ((f + r) & 1) === 1;
13
13
  }
14
- export function createSvgElement(parentOrDoc, name, attrs) {
15
- const doc = parentOrDoc instanceof Document ? parentOrDoc : parentOrDoc.ownerDocument;
16
- const el = doc.createElementNS(SVG_NS, name);
14
+ function _updateElementAttributes(element, attrs) {
17
15
  for (const [key, value] of Object.entries(attrs)) {
18
- el.setAttribute(key, value);
19
- }
20
- if (parentOrDoc instanceof Element) {
21
- parentOrDoc.appendChild(el);
16
+ element.setAttribute(key, value);
22
17
  }
18
+ }
19
+ export function createSvgRootElement(parent, attrs) {
20
+ const doc = parent.ownerDocument;
21
+ const el = doc.createElementNS(SVG_NS, 'svg');
22
+ _updateElementAttributes(el, attrs);
23
+ parent.appendChild(el);
23
24
  return el;
24
25
  }
25
- export function updateElementAttributes(element, attrs) {
26
- for (const [key, value] of Object.entries(attrs)) {
27
- element.setAttribute(key, value);
28
- }
26
+ export function createSvgDefsElement(parent, attrs) {
27
+ const doc = parent.ownerDocument;
28
+ const el = doc.createElementNS(SVG_NS, 'defs');
29
+ _updateElementAttributes(el, attrs);
30
+ // Keep extension-owned defs roots before visual roots.
31
+ const firstNonDefsChild = Array.from(parent.children).find((child) => child.tagName.toLowerCase() !== 'defs');
32
+ parent.insertBefore(el, firstNonDefsChild ?? null);
33
+ return el;
34
+ }
35
+ export function createSvgElement(parent, name, attrs) {
36
+ const doc = parent.ownerDocument;
37
+ const el = doc.createElementNS(SVG_NS, name);
38
+ _updateElementAttributes(el, attrs);
39
+ parent.appendChild(el);
40
+ return el;
41
+ }
42
+ export function updateSvgElementAttributes(element, attrs) {
43
+ _updateElementAttributes(element, attrs);
29
44
  }
30
- export function clearElementChildren(element) {
45
+ export function clearSvgElementChildren(element) {
31
46
  while (element.firstChild) {
32
47
  element.removeChild(element.firstChild);
33
48
  }
@@ -0,0 +1,6 @@
1
+ export interface SvgIdResolver {
2
+ readonly prefix: string;
3
+ makeId(scope: string, localId: string): string;
4
+ makeHref(scope: string, localId: string): string;
5
+ }
6
+ export declare function createSvgIdResolver(): SvgIdResolver;
@@ -0,0 +1,11 @@
1
+ let nextId = 0;
2
+ export function createSvgIdResolver() {
3
+ const prefix = `cb${nextId++}`;
4
+ function makeId(scope, localId) {
5
+ return `${prefix}-${scope}-${localId}`;
6
+ }
7
+ function makeHref(scope, localId) {
8
+ return `#${makeId(scope, localId)}`;
9
+ }
10
+ return { prefix, makeId, makeHref };
11
+ }
@@ -3,9 +3,10 @@ import { RenderFrameSnapshot } from '../extensions/types/basic/render.js';
3
3
  import { TransientInput } from '../extensions/types/basic/transient-visuals.js';
4
4
  import { ExtensionSystemExtensionRecord, ExtensionSystemSharedDataForRenderSystem } from '../extensions/types/main.js';
5
5
  import { Scheduler } from './scheduler/types.js';
6
- export interface SvgRoots extends ExtensionSlotSvgRoots<typeof ALL_EXTENSION_SLOTS> {
6
+ export type RootExtensionSlotName = Exclude<(typeof ALL_EXTENSION_SLOTS)[number], 'defs'>;
7
+ export declare const ROOT_EXTENSION_SLOTS: readonly RootExtensionSlotName[];
8
+ export interface SvgRoots extends ExtensionSlotSvgRoots<typeof ROOT_EXTENSION_SLOTS> {
7
9
  readonly svgRoot: SVGSVGElement;
8
- readonly defs: SVGDefsElement;
9
10
  }
10
11
  export interface RenderExtensionRecordRender {
11
12
  readonly slots: ExtensionAllocatedSlotsInternal;
@@ -24,7 +25,7 @@ export interface RenderSystemInternal {
24
25
  readonly transientVisualsSubscribers: ReadonlySet<string>;
25
26
  }
26
27
  export interface RenderSystemInitOptions {
27
- doc: Document;
28
+ element: HTMLElement;
28
29
  sharedDataFromExtensionSystem: ExtensionSystemSharedDataForRenderSystem;
29
30
  }
30
31
  export interface RenderSystemInitOptionsInternal extends RenderSystemInitOptions {
@@ -1 +1,2 @@
1
- export {};
1
+ import { ALL_EXTENSION_SLOTS } from '../extensions/types/basic/mount.js';
2
+ export const ROOT_EXTENSION_SLOTS = ALL_EXTENSION_SLOTS.filter((slot) => slot !== 'defs');
@@ -1,3 +1,5 @@
1
+ import { DragSessionSnapshot } from '../../state/interaction/types/internal.js';
1
2
  import { RuntimeInteractionSurface } from '../input/controller/types.js';
2
- import { GetInternalState } from '../types/main.js';
3
+ import { GetInternalState, RuntimeInternal } from '../types/main.js';
4
+ export declare function notifyExtensionCancelDragIfOwned(internalState: RuntimeInternal, dragSession: DragSessionSnapshot | null): void;
3
5
  export declare function createRuntimeInteractionSurface(state: GetInternalState): RuntimeInteractionSurface;
@@ -3,6 +3,11 @@ import { isEmptyPieceCode } from '../../state/board/check.js';
3
3
  import { isDragSessionCoreOwned } from '../../state/interaction/helpers.js';
4
4
  import { runtimeRunMutationPipeline } from '../mutation/run.js';
5
5
  import { uiMoveCompleteTo } from './helpers.js';
6
+ export function notifyExtensionCancelDragIfOwned(internalState, dragSession) {
7
+ if (dragSession && !isDragSessionCoreOwned(dragSession)) {
8
+ internalState.extensionSystem.cancelDrag(dragSession);
9
+ }
10
+ }
6
11
  export function createRuntimeInteractionSurface(state) {
7
12
  return {
8
13
  getInteractionStateSnapshot() {
@@ -80,13 +85,17 @@ export function createRuntimeInteractionSurface(state) {
80
85
  cancelActiveInteraction() {
81
86
  const internalState = state();
82
87
  const mutationSession = internalState.mutation.getSession();
83
- internalState.state.interaction.clearActive(mutationSession);
88
+ const interaction = internalState.state.interaction;
89
+ notifyExtensionCancelDragIfOwned(internalState, interaction.dragSession);
90
+ interaction.clearActive(mutationSession);
84
91
  runtimeRunMutationPipeline(internalState);
85
92
  },
86
93
  cancelInteraction() {
87
94
  const internalState = state();
88
95
  const mutationSession = internalState.mutation.getSession();
89
- internalState.state.interaction.clear(mutationSession);
96
+ const interaction = internalState.state.interaction;
97
+ notifyExtensionCancelDragIfOwned(internalState, interaction.dragSession);
98
+ interaction.clear(mutationSession);
90
99
  runtimeRunMutationPipeline(internalState);
91
100
  },
92
101
  updateDragSessionCurrentTarget(target) {
@@ -1,6 +1,6 @@
1
1
  import assert from '@ktarmyshov/assert';
2
2
  import { createExtensionSystem } from '../../extensions/factory/main.js';
3
- import { assertFrameRenderable } from '../../extensions/types/basic/update.js';
3
+ import { isFrameRenderable } from '../../extensions/types/basic/update.js';
4
4
  import { createLayout } from '../../layout/factory.js';
5
5
  import { createRenderSystem } from '../../render/factory.js';
6
6
  import { isNonEmptyPieceCode } from '../../state/board/check.js';
@@ -11,11 +11,11 @@ import { runtimeDestroy, runtimeMount, runtimeUnmount } from '../lifecycle.js';
11
11
  import { createRuntimeMutationPipeline } from '../mutation/factory.js';
12
12
  import { runtimeRunMutationPipeline } from '../mutation/run.js';
13
13
  import { createExtensionRuntimeSurfaceEvents } from './events.js';
14
- import { createRuntimeInteractionSurface } from './input.js';
14
+ import { createRuntimeInteractionSurface, notifyExtensionCancelDragIfOwned } from './input.js';
15
15
  function createRuntimeInternal(options) {
16
16
  const extensionSystem = createExtensionSystem(options);
17
17
  const render = createRenderSystem({
18
- doc: options.doc,
18
+ element: options.element,
19
19
  sharedDataFromExtensionSystem: extensionSystem.getSharedDataForRenderSystem()
20
20
  });
21
21
  return {
@@ -69,9 +69,13 @@ function createExtensionRuntimeSurfaceCommandsInternalSurface(getInternalState)
69
69
  state: state.state.getSnapshot(),
70
70
  layout: state.layout.getSnapshot()
71
71
  };
72
- assertFrameRenderable(renderRequest);
72
+ if (!isFrameRenderable(renderRequest)) {
73
+ return false;
74
+ }
73
75
  state.renderSystem.requestRender(renderRequest);
76
+ return true;
74
77
  }
78
+ return false;
75
79
  },
76
80
  setOrientation(orientation) {
77
81
  const state = getInternalState();
@@ -114,6 +118,7 @@ function createExtensionRuntimeSurfaceCommandsInternalSurface(getInternalState)
114
118
  clearActiveInteraction() {
115
119
  const state = getInternalState();
116
120
  const mutationSession = state.mutation.getSession();
121
+ notifyExtensionCancelDragIfOwned(state, state.state.interaction.dragSession);
117
122
  const changed = state.state.interaction.clearActive(mutationSession);
118
123
  runtimeRunMutationPipeline(state);
119
124
  return changed;
@@ -121,6 +126,7 @@ function createExtensionRuntimeSurfaceCommandsInternalSurface(getInternalState)
121
126
  clearInteraction() {
122
127
  const state = getInternalState();
123
128
  const mutationSession = state.mutation.getSession();
129
+ notifyExtensionCancelDragIfOwned(state, state.state.interaction.dragSession);
124
130
  const changed = state.state.interaction.clear(mutationSession);
125
131
  runtimeRunMutationPipeline(state);
126
132
  return changed;
@@ -1,11 +1,11 @@
1
1
  import { ALL_SCENE_POINTER_EVENT_TYPES } from '../../../extensions/types/basic/events.js';
2
2
  import { pointerEventDestroy, pointerEventHandler } from './pointer.js';
3
3
  export function eventHandler(state, e) {
4
- let context = null;
4
+ let context;
5
5
  if (ALL_SCENE_POINTER_EVENT_TYPES.has(e.type)) {
6
6
  context = pointerEventHandler(state, e);
7
7
  }
8
- if (!context) {
8
+ else {
9
9
  context = {
10
10
  rawEvent: e,
11
11
  sceneEvent: null
@@ -1,6 +1,6 @@
1
- import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
1
+ import { InteractionControllerOnEventContext } from '../controller/types.js';
2
2
  import { InputAdapterInternal } from './types.js';
3
- export declare function pointerEventHandler(state: InputAdapterInternal, e: PointerEvent): ExtensionOnEventContext;
3
+ export declare function pointerEventHandler(state: InputAdapterInternal, e: PointerEvent): InteractionControllerOnEventContext;
4
4
  /** Release capture for the currently tracked pointer and clear tracking state. */
5
5
  export declare function releaseCapture(state: InputAdapterInternal): void;
6
6
  export declare function adapterOnPointerDown(state: InputAdapterInternal, e: PointerEvent): void;
@@ -1,2 +1,2 @@
1
- import { InteractionController, InteractionControllerInitOptions } from './types.js';
1
+ import type { InteractionController, InteractionControllerInitOptions } from './types.js';
2
2
  export declare function createInteractionController(options: InteractionControllerInitOptions): InteractionController;
@@ -1,4 +1,4 @@
1
- import { handlePointerCancel, handlePointerDown, handlePointerMove, handlePointerUp } from './pointer.js';
1
+ import { determineRuntimeInteractionAction } from './interaction.js';
2
2
  import { transmitTransientInput } from './transient-visuals.js';
3
3
  function createInteractionControllerInternal(options) {
4
4
  return {
@@ -9,24 +9,44 @@ export function createInteractionController(options) {
9
9
  const internalState = createInteractionControllerInternal(options);
10
10
  return {
11
11
  onEvent(context) {
12
- internalState.surface.onEvent(context);
12
+ const action = determineRuntimeInteractionAction(internalState, context);
13
+ const extensionContext = {
14
+ ...context,
15
+ runtimeInteractionActionPreview: action
16
+ };
17
+ internalState.surface.onEvent(extensionContext);
13
18
  if (context.rawEvent.defaultPrevented) {
14
19
  transmitTransientInput(internalState, context);
15
20
  return; // The event has been handled by the surface (extensions)
16
21
  }
17
- switch (context.rawEvent.type) {
18
- case 'pointerdown':
19
- handlePointerDown(internalState, context);
20
- break;
21
- case 'pointermove':
22
- handlePointerMove(internalState, context);
23
- break;
24
- case 'pointerup':
25
- handlePointerUp(internalState, context);
26
- break;
27
- case 'pointercancel':
28
- handlePointerCancel(internalState, context);
29
- break;
22
+ if (action) {
23
+ switch (action.type) {
24
+ case 'startLiftedDrag':
25
+ internalState.surface.startLiftedDrag(action.source, action.target);
26
+ break;
27
+ case 'startReleaseTargetingDrag':
28
+ internalState.surface.startReleaseTargetingDrag(action.source, action.target);
29
+ break;
30
+ case 'completeCoreDragTo':
31
+ internalState.surface.completeCoreDragTo(action.target);
32
+ break;
33
+ case 'completeExtensionDrag':
34
+ internalState.surface.completeExtensionDrag(action.target);
35
+ break;
36
+ case 'updateDragSessionCurrentTarget':
37
+ internalState.surface.updateDragSessionCurrentTarget(action.target);
38
+ break;
39
+ case 'cancelActiveInteraction':
40
+ internalState.surface.cancelActiveInteraction();
41
+ break;
42
+ case 'cancelInteraction':
43
+ internalState.surface.cancelInteraction();
44
+ break;
45
+ default:
46
+ throw new Error(
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ `Unhandled RuntimeInteractionAction type: ${String(action.type)}`);
49
+ }
30
50
  }
31
51
  transmitTransientInput(internalState, context);
32
52
  }
@@ -1,3 +1,3 @@
1
1
  import type { Square } from '../../../state/board/types/internal.js';
2
- import { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
2
+ import type { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
3
3
  export declare function canMoveTo(snapshot: InteractionStateSnapshot, target: Square): boolean;
@@ -0,0 +1,3 @@
1
+ import type { RuntimeInteractionAction } from '../../../extensions/types/basic/events.js';
2
+ import type { InteractionControllerInternal, InteractionControllerOnEventContext } from './types.js';
3
+ export declare function determineRuntimeInteractionAction(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): RuntimeInteractionAction | null;
@@ -0,0 +1,14 @@
1
+ import { determineActionPointerCancel, determineActionPointerDown, determineActionPointerMove, determineActionPointerUp } from './pointer.js';
2
+ export function determineRuntimeInteractionAction(state, context) {
3
+ switch (context.rawEvent.type) {
4
+ case 'pointerdown':
5
+ return determineActionPointerDown(state, context);
6
+ case 'pointermove':
7
+ return determineActionPointerMove(state, context);
8
+ case 'pointerup':
9
+ return determineActionPointerUp(state, context);
10
+ case 'pointercancel':
11
+ return determineActionPointerCancel(state, context);
12
+ }
13
+ return null;
14
+ }
@@ -1,6 +1,6 @@
1
- import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
2
- import { InteractionControllerInternal } from './types.js';
3
- export declare function handlePointerDown(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
4
- export declare function handlePointerMove(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
5
- export declare function handlePointerUp(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
6
- export declare function handlePointerCancel(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
1
+ import type { RuntimeInteractionAction } from '../../../extensions/types/basic/events.js';
2
+ import type { InteractionControllerInternal, InteractionControllerOnEventContext } from './types.js';
3
+ export declare function determineActionPointerDown(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): RuntimeInteractionAction | null;
4
+ export declare function determineActionPointerMove(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): RuntimeInteractionAction | null;
5
+ export declare function determineActionPointerUp(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): RuntimeInteractionAction | null;
6
+ export declare function determineActionPointerCancel(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): RuntimeInteractionAction | null;
@@ -3,19 +3,19 @@ import { isEmptyPieceCode, isNonEmptyPieceCode } from '../../../state/board/chec
3
3
  import { fromPieceCode } from '../../../state/board/piece.js';
4
4
  import { isDragSessionCoreOwned } from '../../../state/interaction/helpers.js';
5
5
  import { canMoveTo } from './helpers.js';
6
- export function handlePointerDown(state, context) {
7
- assert(context.rawEvent.type === 'pointerdown', 'handlePointerDown should only be called for pointerdown events');
6
+ export function determineActionPointerDown(state, context) {
7
+ assert(context.rawEvent.type === 'pointerdown', 'determineActionPointerDown should only be called for pointerdown events');
8
8
  const rawEvent = context.rawEvent;
9
9
  if (rawEvent.button !== 0) {
10
10
  // Only handle left-click for now
11
- return;
11
+ return null;
12
12
  }
13
13
  const sceneEvent = context.sceneEvent;
14
14
  assert(sceneEvent, 'Scene event should be present for pointer events');
15
15
  const interaction = state.surface.getInteractionStateSnapshot();
16
16
  if (interaction.dragSession) {
17
17
  // Ignore pointer down events if there's already an active drag session
18
- return;
18
+ return null;
19
19
  }
20
20
  if (sceneEvent.targetSquare !== null) {
21
21
  /**
@@ -28,16 +28,22 @@ export function handlePointerDown(state, context) {
28
28
  assert(isNonEmptyPieceCode(selectedPieceCode), 'Selected piece code must be non-zero');
29
29
  const targetPieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
30
30
  if (isEmptyPieceCode(targetPieceCode)) {
31
- state.surface.startReleaseTargetingDrag(interaction.selected.square, sceneEvent.targetSquare);
32
- return;
31
+ return {
32
+ type: 'startReleaseTargetingDrag',
33
+ source: interaction.selected.square,
34
+ target: sceneEvent.targetSquare
35
+ };
33
36
  }
34
37
  const isLegalMoveTarget = interaction.movability.mode === 1 /* MovabilityModeCode.Free */ ||
35
38
  (interaction.movability.mode === 2 /* MovabilityModeCode.Strict */ &&
36
39
  interaction.activeDestinations.has(sceneEvent.targetSquare));
37
40
  if (fromPieceCode(targetPieceCode).color !== fromPieceCode(selectedPieceCode).color &&
38
41
  isLegalMoveTarget) {
39
- state.surface.startReleaseTargetingDrag(interaction.selected.square, sceneEvent.targetSquare);
40
- return;
42
+ return {
43
+ type: 'startReleaseTargetingDrag',
44
+ source: interaction.selected.square,
45
+ target: sceneEvent.targetSquare
46
+ };
41
47
  }
42
48
  }
43
49
  /**
@@ -46,60 +52,80 @@ export function handlePointerDown(state, context) {
46
52
  */
47
53
  const pieceCode = state.surface.getPieceCodeAt(sceneEvent.targetSquare);
48
54
  if (!isEmptyPieceCode(pieceCode)) {
49
- state.surface.startLiftedDrag(sceneEvent.targetSquare, sceneEvent.targetSquare);
55
+ return {
56
+ type: 'startLiftedDrag',
57
+ source: sceneEvent.targetSquare,
58
+ target: sceneEvent.targetSquare
59
+ };
50
60
  }
51
61
  }
62
+ return null;
52
63
  }
53
- export function handlePointerMove(state, context) {
54
- assert(context.rawEvent.type === 'pointermove', 'handlePointerMove should only be called for pointermove events');
64
+ export function determineActionPointerMove(state, context) {
65
+ assert(context.rawEvent.type === 'pointermove', 'determineActionPointerMove should only be called for pointermove events');
55
66
  const interaction = state.surface.getInteractionStateSnapshot();
56
67
  if (interaction.dragSession) {
57
- state.surface.updateDragSessionCurrentTarget(context.sceneEvent?.targetSquare ?? null);
68
+ return {
69
+ type: 'updateDragSessionCurrentTarget',
70
+ target: context.sceneEvent?.targetSquare ?? null
71
+ };
58
72
  }
73
+ return null;
59
74
  }
60
- export function handlePointerUp(state, context) {
61
- assert(context.rawEvent.type === 'pointerup', 'handlePointerUp should only be called for pointerup events');
75
+ export function determineActionPointerUp(state, context) {
76
+ assert(context.rawEvent.type === 'pointerup', 'determineActionPointerUp should only be called for pointerup events');
62
77
  const interaction = state.surface.getInteractionStateSnapshot();
63
78
  const dragSession = interaction.dragSession;
64
79
  if (!dragSession) {
65
80
  // No active drag session, so nothing to do on pointer up
66
- return;
81
+ return null;
67
82
  }
68
83
  const sceneEvent = context.sceneEvent;
69
84
  assert(sceneEvent, 'Scene event should be present for pointer events');
70
85
  if (!isDragSessionCoreOwned(dragSession)) {
71
86
  // For extension-owned drag sessions, we simply end the session without attempting to make a move,
72
87
  // since the runtime doesn't have enough information about the semantics of the drag session.
73
- state.surface.completeExtensionDrag(sceneEvent.targetSquare);
74
- return;
88
+ return {
89
+ type: 'completeExtensionDrag',
90
+ target: sceneEvent.targetSquare
91
+ };
75
92
  }
76
93
  // Check if the square is the same as the source square of the drag session.
77
94
  // If it is, then we can end the drag session without making a move.
78
95
  if (sceneEvent.targetSquare === dragSession.sourceSquare) {
79
- state.surface.cancelActiveInteraction();
80
- return;
96
+ return {
97
+ type: 'cancelActiveInteraction'
98
+ };
81
99
  }
82
100
  // Check if the target square is a valid destination for the selected piece.
83
101
  if (sceneEvent.targetSquare !== null && canMoveTo(interaction, sceneEvent.targetSquare)) {
84
- state.surface.completeCoreDragTo(sceneEvent.targetSquare);
85
- return;
102
+ return {
103
+ type: 'completeCoreDragTo',
104
+ target: sceneEvent.targetSquare
105
+ };
86
106
  }
87
107
  // Invalid target: piece returns to source for lifted drag (selection preserved),
88
108
  // or selection is cleared for release targeting.
89
109
  if (dragSession.type === 'lifted-piece-drag') {
90
- state.surface.cancelActiveInteraction();
110
+ return {
111
+ type: 'cancelActiveInteraction'
112
+ };
91
113
  }
92
114
  else {
93
- state.surface.cancelInteraction();
115
+ return {
116
+ type: 'cancelInteraction'
117
+ };
94
118
  }
95
119
  }
96
- export function handlePointerCancel(state, context) {
97
- assert(context.rawEvent.type === 'pointercancel', 'handlePointerCancel should only be called for pointercancel events');
120
+ export function determineActionPointerCancel(state, context) {
121
+ assert(context.rawEvent.type === 'pointercancel', 'determineActionPointerCancel should only be called for pointercancel events');
98
122
  const interaction = state.surface.getInteractionStateSnapshot();
99
123
  if (!interaction.dragSession) {
100
124
  // No active drag session, so nothing to do on pointer cancel
101
- return;
125
+ return null;
102
126
  }
103
127
  // Cancel the active interaction on pointer cancel
104
- state.surface.cancelActiveInteraction();
128
+ return {
129
+ type: 'cancelActiveInteraction'
130
+ };
105
131
  }
@@ -1,3 +1,2 @@
1
- import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
2
- import { InteractionControllerInternal } from './types.js';
3
- export declare function transmitTransientInput(state: InteractionControllerInternal, context: ExtensionOnEventContext): void;
1
+ import type { InteractionControllerInternal, InteractionControllerOnEventContext } from './types.js';
2
+ export declare function transmitTransientInput(state: InteractionControllerInternal, context: InteractionControllerOnEventContext): void;
@@ -1,7 +1,7 @@
1
- import { TransientInput } from '../../../extensions/types/basic/transient-visuals.js';
2
- import { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
3
- import { PieceCode, Square } from '../../../state/board/types/internal.js';
4
- import { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
1
+ import type { TransientInput } from '../../../extensions/types/basic/transient-visuals.js';
2
+ import type { ExtensionOnEventContext } from '../../../extensions/types/context/events.js';
3
+ import type { PieceCode, Square } from '../../../state/board/types/internal.js';
4
+ import type { InteractionStateSnapshot } from '../../../state/interaction/types/main.js';
5
5
  export interface RuntimeInteractionSurface {
6
6
  getInteractionStateSnapshot(): InteractionStateSnapshot;
7
7
  getPieceCodeAt(square: Square): PieceCode;
@@ -21,6 +21,7 @@ export interface InteractionControllerInternal {
21
21
  export interface InteractionControllerInitOptions {
22
22
  surface: RuntimeInteractionSurface;
23
23
  }
24
+ export type InteractionControllerOnEventContext = Omit<ExtensionOnEventContext, 'runtimeInteractionActionPreview'>;
24
25
  export interface InteractionController {
25
- onEvent(context: ExtensionOnEventContext): void;
26
+ onEvent(context: InteractionControllerOnEventContext): void;
26
27
  }
@@ -14,7 +14,7 @@ export interface RuntimeInternal extends RuntimeMutationPipelineContext {
14
14
  }
15
15
  export type GetInternalState = () => RuntimeInternal;
16
16
  export interface RuntimeInitOptions {
17
- doc: Document;
17
+ element: HTMLElement;
18
18
  state?: RuntimeStateInitOptions;
19
19
  extensions?: readonly AnyExtensionDefinition[];
20
20
  }