diff --git a/benchmarks/conformance/previews/manifest.json b/benchmarks/conformance/previews/manifest.json index 5ad78811..300c9bf6 100644 --- a/benchmarks/conformance/previews/manifest.json +++ b/benchmarks/conformance/previews/manifest.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "width": 288, "height": 192, - "sourceHash": "5cb9f7c0512bf98f18bb88dbb7c2ec524b0239530a64d5a9b1d5a00ab5f9b3d3", + "sourceHash": "82eecd6c1fbdca5e857b965e09bb53c550520b2e596fbc4e9850266f03fdb9b4", "assets": [ { "id": "01-line-gaps", diff --git a/packages/charts-core/src/motion.test.ts b/packages/charts-core/src/motion.test.ts index c17a5926..ef4049b0 100644 --- a/packages/charts-core/src/motion.test.ts +++ b/packages/charts-core/src/motion.test.ts @@ -2398,6 +2398,74 @@ describe('SVG motion', () => { frames.restore() }) + it('advances the crosshair spring during repeated data-frame repaints', () => { + const frames = installManagedFrames() + const makeScene = (offset: number) => + createChartScene( + defineChart({ + marks: [ + lineY( + Array.from({ length: 20 }, (_, x) => ({ x, y: x + offset })), + { x: 'x', y: 'y', key: 'x' }, + ), + crosshair({ x: true, y: false }), + ], + scales: { + x: { scale: scaleLinear().domain([0, 19]) }, + y: { scale: scaleLinear().domain([0, 40]) }, + }, + guides: false, + }), + { width: 400, height: 200 }, + ) + const container = document.createElement('div') + const surface = motion({ + initial: false, + transition: { type: 'spring', stiffness: 170, damping: 18 }, + }).mount(container, () => {}) + let unsubscribe = () => {} + try { + surface.render(makeScene(0), { ariaLabel: 'Crosshair repaint' }) + const next = makeScene(5) + surface.render(next, { ariaLabel: 'Crosshair repaint' }) + const paint = (index: number) => { + const point = next.points[index]! + surface.paintFocus({ + primary: point, + group: [point], + source: 'pointer', + pinned: false, + }) + } + paint(2) + paint(15) + const line = container.querySelector( + '.ts-chart__focus-guide-layer line', + )! + const start = Number(line.getAttribute('x1')) + let repaints = 0 + if (!surface.subscribePresentationPoints) { + throw new Error('Expected motion presentation subscriptions') + } + unsubscribe = surface.subscribePresentationPoints(() => { + repaints += 1 + paint(15) + }) + for (let time = 16; time <= 128; time += 16) frames.runAll(time) + expect(repaints).toBe(8) + expect(Number(line.getAttribute('x1'))).toBeGreaterThan(start + 100) + expect(Number(line.getAttribute('x1'))).toBeLessThan(next.points[15]!.x) + expect(container.querySelector('.ts-chart__focus-guide-layer line')).toBe( + line, + ) + } finally { + unsubscribe() + surface.destroy() + expect(frames.pending()).toBe(0) + frames.restore() + } + }) + it('retargets a persistent crosshair spring without replacing guide elements', () => { const transition = { type: 'spring' as const,