@geometra/renderer-canvas 1.53.0 → 1.54.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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gestures.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gestures.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gestures.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { attachGestureRecognizers } from '../gestures.js';
3
+ class FakeEventTarget {
4
+ listeners = new Map();
5
+ addEventListener(type, listener) {
6
+ if (!this.listeners.has(type))
7
+ this.listeners.set(type, new Set());
8
+ this.listeners.get(type).add(listener);
9
+ }
10
+ removeEventListener(type, listener) {
11
+ this.listeners.get(type)?.delete(listener);
12
+ }
13
+ dispatch(type, event) {
14
+ const list = this.listeners.get(type);
15
+ if (!list)
16
+ return;
17
+ for (const listener of list)
18
+ listener(event);
19
+ }
20
+ listenerCount(type) {
21
+ return this.listeners.get(type)?.size ?? 0;
22
+ }
23
+ }
24
+ function makeRecorder() {
25
+ const events = [];
26
+ return {
27
+ events,
28
+ pointerDown: sample => events.push({ type: 'down', sample }),
29
+ pointerMove: sample => events.push({ type: 'move', sample }),
30
+ pointerUp: sample => events.push({ type: 'up', sample }),
31
+ pointerCancel: pointerId => events.push({ type: 'cancel', pointerId }),
32
+ };
33
+ }
34
+ function makeCanvas(target, rect = { left: 10, top: 20 }) {
35
+ return {
36
+ addEventListener: target.addEventListener.bind(target),
37
+ removeEventListener: target.removeEventListener.bind(target),
38
+ getBoundingClientRect: () => rect,
39
+ };
40
+ }
41
+ describe('attachGestureRecognizers', () => {
42
+ it('converts pointerdown clientX/Y to canvas-space samples and fans out to all recognizers', () => {
43
+ const canvasTarget = new FakeEventTarget();
44
+ const docTarget = new FakeEventTarget();
45
+ const canvas = makeCanvas(canvasTarget, { left: 10, top: 20 });
46
+ const a = makeRecorder();
47
+ const b = makeRecorder();
48
+ const cleanup = attachGestureRecognizers(canvas, [a, b], {
49
+ documentTarget: docTarget,
50
+ now: () => 42,
51
+ });
52
+ canvasTarget.dispatch('pointerdown', { pointerId: 1, clientX: 30, clientY: 50 });
53
+ expect(a.events).toEqual([{ type: 'down', sample: { id: 1, x: 20, y: 30, timestampMs: 42 } }]);
54
+ expect(b.events).toEqual([{ type: 'down', sample: { id: 1, x: 20, y: 30, timestampMs: 42 } }]);
55
+ cleanup();
56
+ });
57
+ it('routes pointermove/up/cancel through documentTarget when trackOutsideCanvas is true (default)', () => {
58
+ const canvasTarget = new FakeEventTarget();
59
+ const docTarget = new FakeEventTarget();
60
+ const canvas = makeCanvas(canvasTarget);
61
+ const r = makeRecorder();
62
+ const cleanup = attachGestureRecognizers(canvas, [r], {
63
+ documentTarget: docTarget,
64
+ now: () => 0,
65
+ });
66
+ canvasTarget.dispatch('pointerdown', { pointerId: 7, clientX: 10, clientY: 20 });
67
+ docTarget.dispatch('pointermove', { pointerId: 7, clientX: 60, clientY: 80 });
68
+ docTarget.dispatch('pointerup', { pointerId: 7, clientX: 60, clientY: 80 });
69
+ expect(r.events.map(e => e.type)).toEqual(['down', 'move', 'up']);
70
+ // Moves for unknown pointer ids are filtered out.
71
+ docTarget.dispatch('pointermove', { pointerId: 99, clientX: 0, clientY: 0 });
72
+ expect(r.events.map(e => e.type)).toEqual(['down', 'move', 'up']);
73
+ cleanup();
74
+ expect(canvasTarget.listenerCount('pointerdown')).toBe(0);
75
+ expect(docTarget.listenerCount('pointermove')).toBe(0);
76
+ expect(docTarget.listenerCount('pointerup')).toBe(0);
77
+ expect(docTarget.listenerCount('pointercancel')).toBe(0);
78
+ });
79
+ it('clamps all listeners to the canvas when trackOutsideCanvas is false', () => {
80
+ const canvasTarget = new FakeEventTarget();
81
+ const docTarget = new FakeEventTarget();
82
+ const canvas = makeCanvas(canvasTarget);
83
+ const r = makeRecorder();
84
+ const cleanup = attachGestureRecognizers(canvas, [r], {
85
+ trackOutsideCanvas: false,
86
+ documentTarget: docTarget,
87
+ now: () => 0,
88
+ });
89
+ canvasTarget.dispatch('pointerdown', { pointerId: 1, clientX: 10, clientY: 20 });
90
+ docTarget.dispatch('pointermove', { pointerId: 1, clientX: 60, clientY: 80 });
91
+ // Document move ignored because we only listen on canvas.
92
+ expect(r.events.map(e => e.type)).toEqual(['down']);
93
+ canvasTarget.dispatch('pointermove', { pointerId: 1, clientX: 60, clientY: 80 });
94
+ expect(r.events.map(e => e.type)).toEqual(['down', 'move']);
95
+ cleanup();
96
+ });
97
+ it('pointercancel forwards the pointer id and drops the active pointer', () => {
98
+ const canvasTarget = new FakeEventTarget();
99
+ const docTarget = new FakeEventTarget();
100
+ const canvas = makeCanvas(canvasTarget);
101
+ const r = makeRecorder();
102
+ const cleanup = attachGestureRecognizers(canvas, [r], {
103
+ documentTarget: docTarget,
104
+ now: () => 0,
105
+ });
106
+ canvasTarget.dispatch('pointerdown', { pointerId: 3, clientX: 10, clientY: 20 });
107
+ docTarget.dispatch('pointercancel', { pointerId: 3 });
108
+ expect(r.events[r.events.length - 1]).toEqual({ type: 'cancel', pointerId: 3 });
109
+ // After cancel, further moves for this id are ignored.
110
+ docTarget.dispatch('pointermove', { pointerId: 3, clientX: 100, clientY: 100 });
111
+ expect(r.events.map(e => e.type)).toEqual(['down', 'cancel']);
112
+ cleanup();
113
+ });
114
+ it('uses the options.now timestamp source for every sample', () => {
115
+ const canvasTarget = new FakeEventTarget();
116
+ const docTarget = new FakeEventTarget();
117
+ const canvas = makeCanvas(canvasTarget);
118
+ const r = makeRecorder();
119
+ let t = 0;
120
+ const clock = () => {
121
+ t += 10;
122
+ return t;
123
+ };
124
+ const cleanup = attachGestureRecognizers(canvas, [r], {
125
+ documentTarget: docTarget,
126
+ now: clock,
127
+ });
128
+ canvasTarget.dispatch('pointerdown', { pointerId: 1, clientX: 10, clientY: 20 });
129
+ docTarget.dispatch('pointermove', { pointerId: 1, clientX: 12, clientY: 22 });
130
+ expect(r.events.map(e => e.sample?.timestampMs)).toEqual([10, 20]);
131
+ cleanup();
132
+ });
133
+ });
134
+ //# sourceMappingURL=gestures.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gestures.test.js","sourceRoot":"","sources":["../../src/__tests__/gestures.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { PointerSample } from '@geometra/core';
2
+ /**
3
+ * Host-agnostic surface that every gesture recognizer in `@geometra/core`
4
+ * implements. We redeclare it here rather than importing each recognizer type
5
+ * so callers can pass any array of compatible recognizers (including user-built
6
+ * ones) without having to list every union member.
7
+ */
8
+ export interface CanvasGestureRecognizerLike {
9
+ pointerDown(sample: PointerSample): void;
10
+ pointerMove(sample: PointerSample): void;
11
+ pointerUp(sample: PointerSample): void;
12
+ pointerCancel(pointerId: number): void;
13
+ }
14
+ export interface AttachGestureRecognizersOptions {
15
+ /**
16
+ * When true (default), `pointermove` / `pointerup` / `pointercancel` are
17
+ * attached to `document` so drags continue if the pointer leaves the canvas.
18
+ * Set false to clamp all events to the canvas bounds (useful in test
19
+ * environments without `document`).
20
+ */
21
+ trackOutsideCanvas?: boolean;
22
+ /**
23
+ * High-resolution timestamp source. Defaults to `performance.now()` — or a
24
+ * frozen `0` when `performance` is unavailable (SSR/Node test envs).
25
+ */
26
+ now?: () => number;
27
+ /**
28
+ * Optional override for the document-like target used for outside-canvas
29
+ * tracking. Primarily for tests that can't rely on a global `document`.
30
+ */
31
+ documentTarget?: {
32
+ addEventListener: Document['addEventListener'];
33
+ removeEventListener: Document['removeEventListener'];
34
+ };
35
+ }
36
+ /**
37
+ * Convert browser `PointerEvent`s into {@link PointerSample}s and fan them out
38
+ * to one or more `@geometra/core` gesture recognizers (pan / swipe / pinch or
39
+ * user-built state machines). Returns a cleanup function.
40
+ *
41
+ * Typical integration:
42
+ *
43
+ * ```ts
44
+ * import { createPanRecognizer } from '@geometra/core'
45
+ * import { attachGestureRecognizers } from '@geometra/renderer-canvas'
46
+ *
47
+ * const pan = createPanRecognizer({ onMove: e => setOffset(e.deltaX, e.deltaY) })
48
+ * const stop = attachGestureRecognizers(canvas, [pan])
49
+ * // ...later: stop()
50
+ * ```
51
+ *
52
+ * `pointerdown` is always attached to the canvas. `pointermove` /
53
+ * `pointerup` / `pointercancel` are attached to `document` by default so drags
54
+ * continue after the pointer leaves the canvas — the sample coordinates stay
55
+ * relative to the canvas because we subtract `getBoundingClientRect()` on every
56
+ * event. Set `trackOutsideCanvas: false` to clamp to the canvas.
57
+ *
58
+ * We track which pointer IDs have been seen via `pointerdown` on this canvas so
59
+ * stray moves/ups from unrelated elements can't accidentally drive recognizer
60
+ * state.
61
+ */
62
+ export declare function attachGestureRecognizers(canvas: HTMLCanvasElement, recognizers: ReadonlyArray<CanvasGestureRecognizerLike>, options?: AttachGestureRecognizersOptions): () => void;
63
+ //# sourceMappingURL=gestures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gestures.d.ts","sourceRoot":"","sources":["../src/gestures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACxC,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACxC,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACtC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,+BAA+B;IAC9C;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE;QACf,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAA;QAC9C,mBAAmB,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAA;KACrD,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,aAAa,CAAC,2BAA2B,CAAC,EACvD,OAAO,GAAE,+BAAoC,GAC5C,MAAM,IAAI,CA2DZ"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Convert browser `PointerEvent`s into {@link PointerSample}s and fan them out
3
+ * to one or more `@geometra/core` gesture recognizers (pan / swipe / pinch or
4
+ * user-built state machines). Returns a cleanup function.
5
+ *
6
+ * Typical integration:
7
+ *
8
+ * ```ts
9
+ * import { createPanRecognizer } from '@geometra/core'
10
+ * import { attachGestureRecognizers } from '@geometra/renderer-canvas'
11
+ *
12
+ * const pan = createPanRecognizer({ onMove: e => setOffset(e.deltaX, e.deltaY) })
13
+ * const stop = attachGestureRecognizers(canvas, [pan])
14
+ * // ...later: stop()
15
+ * ```
16
+ *
17
+ * `pointerdown` is always attached to the canvas. `pointermove` /
18
+ * `pointerup` / `pointercancel` are attached to `document` by default so drags
19
+ * continue after the pointer leaves the canvas — the sample coordinates stay
20
+ * relative to the canvas because we subtract `getBoundingClientRect()` on every
21
+ * event. Set `trackOutsideCanvas: false` to clamp to the canvas.
22
+ *
23
+ * We track which pointer IDs have been seen via `pointerdown` on this canvas so
24
+ * stray moves/ups from unrelated elements can't accidentally drive recognizer
25
+ * state.
26
+ */
27
+ export function attachGestureRecognizers(canvas, recognizers, options = {}) {
28
+ const trackOutsideCanvas = options.trackOutsideCanvas !== false;
29
+ const now = options.now ?? fallbackNow();
30
+ const activePointers = new Set();
31
+ function toSample(e) {
32
+ const rect = canvas.getBoundingClientRect();
33
+ return {
34
+ id: e.pointerId,
35
+ x: e.clientX - rect.left,
36
+ y: e.clientY - rect.top,
37
+ timestampMs: now(),
38
+ };
39
+ }
40
+ function onPointerDown(e) {
41
+ activePointers.add(e.pointerId);
42
+ const sample = toSample(e);
43
+ for (const r of recognizers)
44
+ r.pointerDown(sample);
45
+ }
46
+ function onPointerMove(e) {
47
+ if (!activePointers.has(e.pointerId))
48
+ return;
49
+ const sample = toSample(e);
50
+ for (const r of recognizers)
51
+ r.pointerMove(sample);
52
+ }
53
+ function onPointerUp(e) {
54
+ if (!activePointers.has(e.pointerId))
55
+ return;
56
+ activePointers.delete(e.pointerId);
57
+ const sample = toSample(e);
58
+ for (const r of recognizers)
59
+ r.pointerUp(sample);
60
+ }
61
+ function onPointerCancel(e) {
62
+ if (!activePointers.has(e.pointerId))
63
+ return;
64
+ activePointers.delete(e.pointerId);
65
+ for (const r of recognizers)
66
+ r.pointerCancel(e.pointerId);
67
+ }
68
+ canvas.addEventListener('pointerdown', onPointerDown);
69
+ const moveTarget = trackOutsideCanvas
70
+ ? (options.documentTarget ?? (typeof document !== 'undefined' ? document : canvas))
71
+ : canvas;
72
+ moveTarget.addEventListener('pointermove', onPointerMove);
73
+ moveTarget.addEventListener('pointerup', onPointerUp);
74
+ moveTarget.addEventListener('pointercancel', onPointerCancel);
75
+ return () => {
76
+ canvas.removeEventListener('pointerdown', onPointerDown);
77
+ moveTarget.removeEventListener('pointermove', onPointerMove);
78
+ moveTarget.removeEventListener('pointerup', onPointerUp);
79
+ moveTarget.removeEventListener('pointercancel', onPointerCancel);
80
+ };
81
+ }
82
+ function fallbackNow() {
83
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
84
+ return () => performance.now();
85
+ }
86
+ return () => 0;
87
+ }
88
+ //# sourceMappingURL=gestures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gestures.js","sourceRoot":"","sources":["../src/gestures.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,4 +2,6 @@ export { createBrowserCanvasClient } from './browser-client.js';
2
2
  export type { BrowserCanvasClientHandle, BrowserCanvasClientOptions } from './browser-client.js';
3
3
  export { CanvasRenderer, enableSelection, enableFind, enableAccessibilityMirror, enableInputForwarding } from './renderer.js';
4
4
  export type { CanvasRendererOptions, AccessibilityMirrorOptions, CanvasInputForwardingOptions } from './renderer.js';
5
+ export { attachGestureRecognizers } from './gestures.js';
6
+ export type { AttachGestureRecognizersOptions, CanvasGestureRecognizerLike } from './gestures.js';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC7H,YAAY,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAC7H,YAAY,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAA;AACpH,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AACxD,YAAY,EAAE,+BAA+B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { createBrowserCanvasClient } from './browser-client.js';
2
2
  export { CanvasRenderer, enableSelection, enableFind, enableAccessibilityMirror, enableInputForwarding } from './renderer.js';
3
+ export { attachGestureRecognizers } from './gestures.js';
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE7H,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geometra/renderer-canvas",
3
- "version": "1.53.0",
3
+ "version": "1.54.0",
4
4
  "description": "Canvas2D renderer for Geometra — the geometry protocol for UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,6 +35,6 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@geometra/client": "^1.6.0",
38
- "@geometra/core": "^1.53.0"
38
+ "@geometra/core": "^1.54.0"
39
39
  }
40
40
  }