@opendata-ai/openchart-vanilla 8.0.0-rc.2 → 8.0.0-rc.21
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.
- package/dist/{chunk-W34KN7FD.js → chunk-2NAQRP66.js} +2 -2
- package/dist/chunk-DCODCLWI.js +234 -0
- package/dist/chunk-DCODCLWI.js.map +1 -0
- package/dist/{chunk-5EOV5JQZ.js → chunk-EKAUQAAX.js} +50 -46
- package/dist/chunk-EKAUQAAX.js.map +1 -0
- package/dist/{chunk-ENXSUXC5.js → chunk-KNT5AZ2O.js} +7309 -5490
- package/dist/chunk-KNT5AZ2O.js.map +1 -0
- package/dist/chunk-RM5XS6NR.js +113 -0
- package/dist/chunk-RM5XS6NR.js.map +1 -0
- package/dist/chunk-T32UHMZA.js +282 -0
- package/dist/chunk-T32UHMZA.js.map +1 -0
- package/dist/export-gif.d.ts +113 -0
- package/dist/export-gif.js +21 -0
- package/dist/export-gif.js.map +1 -0
- package/dist/index.d.ts +283 -24
- package/dist/index.js +2012 -763
- package/dist/index.js.map +1 -1
- package/dist/{mount-CUKQLeqD.d.ts → mount-Dvv8NYIv.d.ts} +117 -3
- package/dist/simulation-worker.js +83 -2
- package/dist/static.js +7 -69
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +3 -2
- package/dist/story/index.js +5 -3
- package/dist/story/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +14 -6
- package/dist/chunk-5EOV5JQZ.js.map +0 -1
- package/dist/chunk-ENXSUXC5.js.map +0 -1
- package/src/__test-fixtures__/dom.ts +0 -70
- package/src/__test-fixtures__/specs.ts +0 -214
- package/src/__tests__/animation.test.ts +0 -358
- package/src/__tests__/annotations-render.test.ts +0 -201
- package/src/__tests__/compound-labels.test.ts +0 -122
- package/src/__tests__/continuous-legend.test.ts +0 -91
- package/src/__tests__/crosshair.test.ts +0 -130
- package/src/__tests__/edit-events.test.ts +0 -902
- package/src/__tests__/events.test.ts +0 -387
- package/src/__tests__/export.test.ts +0 -241
- package/src/__tests__/font-race.test.ts +0 -199
- package/src/__tests__/gradient-ids.test.ts +0 -159
- package/src/__tests__/gradient-utils.test.ts +0 -125
- package/src/__tests__/known-bugs-render.test.ts +0 -183
- package/src/__tests__/map.test.ts +0 -293
- package/src/__tests__/mount.test.ts +0 -357
- package/src/__tests__/pattern-fills.test.ts +0 -105
- package/src/__tests__/resize-timing.test.ts +0 -218
- package/src/__tests__/sankey.test.ts +0 -133
- package/src/__tests__/selection-events.test.ts +0 -869
- package/src/__tests__/series-search.test.ts +0 -311
- package/src/__tests__/static.test.ts +0 -141
- package/src/__tests__/svg-renderer.test.ts +0 -895
- package/src/__tests__/table-keyboard.test.ts +0 -361
- package/src/__tests__/table-mount.test.ts +0 -639
- package/src/__tests__/tilemap.test.ts +0 -158
- package/src/__tests__/tooltip.test.ts +0 -337
- package/src/__tests__/transition.test.ts +0 -1777
- package/src/__tests__/you-draw-it.test.ts +0 -273
- package/src/animation-vars.ts +0 -42
- package/src/animation.ts +0 -76
- package/src/barlist-mount.ts +0 -383
- package/src/barlist-renderer.ts +0 -267
- package/src/dom-helpers.ts +0 -17
- package/src/export.ts +0 -482
- package/src/gradient-utils.ts +0 -138
- package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
- package/src/graph/__tests__/graph-mount.test.ts +0 -213
- package/src/graph/__tests__/interaction.test.ts +0 -205
- package/src/graph/__tests__/keyboard.test.ts +0 -653
- package/src/graph/__tests__/search.test.ts +0 -88
- package/src/graph/__tests__/simulation.test.ts +0 -233
- package/src/graph/__tests__/spatial-index.test.ts +0 -142
- package/src/graph/__tests__/zoom.test.ts +0 -203
- package/src/graph/canvas-renderer.ts +0 -747
- package/src/graph/interaction.ts +0 -367
- package/src/graph/keyboard.ts +0 -208
- package/src/graph/search.ts +0 -50
- package/src/graph/simulation-worker-url.ts +0 -12
- package/src/graph/simulation-worker.ts +0 -278
- package/src/graph/simulation.ts +0 -422
- package/src/graph/spatial-index.ts +0 -121
- package/src/graph/types.ts +0 -47
- package/src/graph/worker-protocol.ts +0 -70
- package/src/graph/zoom.ts +0 -134
- package/src/graph-mount.ts +0 -888
- package/src/index.ts +0 -76
- package/src/interactions/chart-events.ts +0 -139
- package/src/interactions/crosshair.ts +0 -233
- package/src/interactions/drag-handler.ts +0 -175
- package/src/interactions/editing-drags.ts +0 -515
- package/src/interactions/index.ts +0 -25
- package/src/interactions/keyboard-nav.ts +0 -111
- package/src/interactions/legend-interaction.ts +0 -46
- package/src/interactions/selection.ts +0 -280
- package/src/interactions/tooltip-events.ts +0 -72
- package/src/map-camera.ts +0 -88
- package/src/map-mount.ts +0 -701
- package/src/map-renderer.ts +0 -338
- package/src/map-transition.ts +0 -83
- package/src/measure-text.ts +0 -134
- package/src/mount.ts +0 -1382
- package/src/pattern-utils.ts +0 -110
- package/src/renderers/annotations.ts +0 -388
- package/src/renderers/axes.ts +0 -351
- package/src/renderers/brand.ts +0 -87
- package/src/renderers/chrome.ts +0 -196
- package/src/renderers/endpoint-labels.ts +0 -150
- package/src/renderers/legend.ts +0 -311
- package/src/renderers/marks.ts +0 -561
- package/src/renderers/metrics.ts +0 -63
- package/src/renderers/svg-dom.ts +0 -40
- package/src/renderers/table-cells.ts +0 -446
- package/src/resize-observer.ts +0 -46
- package/src/sankey-mount.ts +0 -622
- package/src/sankey-renderer.ts +0 -614
- package/src/series-search.ts +0 -392
- package/src/static.ts +0 -251
- package/src/story/__tests__/camera-math.test.ts +0 -145
- package/src/story/__tests__/create-chart-story.test.ts +0 -317
- package/src/story/__tests__/progress-math.test.ts +0 -95
- package/src/story/__tests__/scroll-driver.test.ts +0 -130
- package/src/story/__tests__/tween.test.ts +0 -159
- package/src/story/camera-math.ts +0 -125
- package/src/story/create-chart-story.ts +0 -353
- package/src/story/crossfade.ts +0 -70
- package/src/story/index.ts +0 -39
- package/src/story/progress-math.ts +0 -85
- package/src/story/resolve-camera-target.ts +0 -119
- package/src/story/scroll-driver.ts +0 -206
- package/src/story/story-camera.ts +0 -49
- package/src/story/tween.ts +0 -112
- package/src/story/types.ts +0 -76
- package/src/svg-ids.ts +0 -23
- package/src/svg-renderer.ts +0 -383
- package/src/table-keyboard.ts +0 -266
- package/src/table-mount.ts +0 -567
- package/src/table-renderer.ts +0 -414
- package/src/text-edit-overlay.ts +0 -255
- package/src/theme-tokens.ts +0 -51
- package/src/tilemap-mount.ts +0 -511
- package/src/tilemap-renderer.ts +0 -396
- package/src/tooltip.ts +0 -177
- package/src/transition.ts +0 -2816
- package/src/you-draw-it.ts +0 -480
- /package/dist/{chunk-W34KN7FD.js.map → chunk-2NAQRP66.js.map} +0 -0
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "You draw it" (`youDrawIt`) interaction tests.
|
|
3
|
-
*
|
|
4
|
-
* Mounts real charts via createChart into a happy-dom container and drives the
|
|
5
|
-
* drawing overlay with real mouse/touch events, asserting on visible behavior:
|
|
6
|
-
* the reader's guess path, the reveal clip animation, onReveal payload in data
|
|
7
|
-
* coordinates, the keyboard-reachable skip button, and reduced-motion.
|
|
8
|
-
*
|
|
9
|
-
* happy-dom has no layout engine, so the SVG's getBoundingClientRect is the
|
|
10
|
-
* zero rect and viewBox.baseVal is empty. Our pointer math then falls back to
|
|
11
|
-
* scale 1 with a (0,0) origin, so clientX maps directly to SVG user units:
|
|
12
|
-
* dispatching at clientX === sample.px snaps the guess to that sample.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { ChartSpec } from '@opendata-ai/openchart-core';
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
-
import { createContainer } from '../__test-fixtures__/dom';
|
|
18
|
-
import { type ChartInstance, createChart } from '../mount';
|
|
19
|
-
|
|
20
|
-
function lineData() {
|
|
21
|
-
return [2000, 2005, 2010, 2015, 2020].map((year, i) => ({
|
|
22
|
-
year: `${year}`,
|
|
23
|
-
value: 100 + i * 10,
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function makeSpec(overrides: Partial<ChartSpec> = {}): ChartSpec {
|
|
28
|
-
return {
|
|
29
|
-
mark: 'line',
|
|
30
|
-
data: lineData(),
|
|
31
|
-
encoding: {
|
|
32
|
-
x: { field: 'year', type: 'temporal' },
|
|
33
|
-
y: { field: 'value', type: 'quantitative' },
|
|
34
|
-
},
|
|
35
|
-
youDrawIt: { from: '2010' },
|
|
36
|
-
animation: false,
|
|
37
|
-
...overrides,
|
|
38
|
-
} as ChartSpec;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function overlay(container: HTMLElement): SVGRectElement | null {
|
|
42
|
-
return container.querySelector('[data-ydi-overlay]');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function guessPath(container: HTMLElement): SVGPathElement | null {
|
|
46
|
-
return container.querySelector('[data-ydi-guess-path]');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function clipRect(container: HTMLElement): SVGRectElement | null {
|
|
50
|
-
return container.querySelector('[data-ydi-clip-rect]');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function drawAt(target: EventTarget, clientX: number, clientY: number): void {
|
|
54
|
-
target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX, clientY }));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function dragTo(clientX: number, clientY: number): void {
|
|
58
|
-
document.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX, clientY }));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function releaseMouse(): void {
|
|
62
|
-
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
describe('you draw it', () => {
|
|
66
|
-
let container: HTMLDivElement;
|
|
67
|
-
let chart: ChartInstance | null = null;
|
|
68
|
-
|
|
69
|
-
beforeEach(() => {
|
|
70
|
-
container = createContainer();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
afterEach(() => {
|
|
74
|
-
chart?.destroy();
|
|
75
|
-
chart = null;
|
|
76
|
-
document.body.innerHTML = '';
|
|
77
|
-
vi.restoreAllMocks();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('renders the hatched drawing region, prompt, and a keyboard-reachable reveal button', () => {
|
|
81
|
-
chart = createChart(container, makeSpec({ youDrawIt: { from: '2010', prompt: 'Guess it' } }));
|
|
82
|
-
|
|
83
|
-
expect(container.querySelector('[data-ydi-region]')).not.toBeNull();
|
|
84
|
-
expect(overlay(container)).not.toBeNull();
|
|
85
|
-
|
|
86
|
-
const promptEl = container.querySelector('.oc-ydi-prompt');
|
|
87
|
-
expect(promptEl?.textContent).toBe('Guess it');
|
|
88
|
-
|
|
89
|
-
const button = container.querySelector('.oc-ydi-reveal-button') as HTMLButtonElement;
|
|
90
|
-
expect(button).not.toBeNull();
|
|
91
|
-
// Native button is focusable (keyboard reachable) without a manual tabindex.
|
|
92
|
-
expect(button.tagName).toBe('BUTTON');
|
|
93
|
-
expect(button.disabled).toBe(false);
|
|
94
|
-
button.focus();
|
|
95
|
-
expect(document.activeElement).toBe(button);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('draws a guess snapped to x-samples as the reader drags', () => {
|
|
99
|
-
chart = createChart(container, makeSpec());
|
|
100
|
-
const samples = chart.layout.youDrawIt!.samples;
|
|
101
|
-
expect(samples.length).toBe(3); // 2010, 2015, 2020
|
|
102
|
-
|
|
103
|
-
const rect = overlay(container)!;
|
|
104
|
-
// Drag across all three samples at distinct y heights.
|
|
105
|
-
drawAt(rect, samples[0].px, 120);
|
|
106
|
-
dragTo(samples[1].px, 160);
|
|
107
|
-
dragTo(samples[2].px, 200);
|
|
108
|
-
releaseMouse();
|
|
109
|
-
|
|
110
|
-
const d = guessPath(container)!.getAttribute('d') ?? '';
|
|
111
|
-
// One M + two L commands = three snapped points.
|
|
112
|
-
const commands = d.match(/[ML]/g) ?? [];
|
|
113
|
-
expect(commands.length).toBe(3);
|
|
114
|
-
// Points sit exactly on the sample x positions.
|
|
115
|
-
for (const s of samples) {
|
|
116
|
-
expect(d).toContain(`${s.px},`);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('clamps a guess drawn out of bounds to the plot area', () => {
|
|
121
|
-
chart = createChart(container, makeSpec());
|
|
122
|
-
const ydi = chart.layout.youDrawIt!;
|
|
123
|
-
const rect = overlay(container)!;
|
|
124
|
-
|
|
125
|
-
// Drag far below the bottom of the area.
|
|
126
|
-
drawAt(rect, ydi.samples[0].px, ydi.area.y + ydi.area.height + 500);
|
|
127
|
-
releaseMouse();
|
|
128
|
-
|
|
129
|
-
const d = guessPath(container)!.getAttribute('d') ?? '';
|
|
130
|
-
const match = d.match(/M[\d.]+,([\d.]+)/);
|
|
131
|
-
expect(match).not.toBeNull();
|
|
132
|
-
const y = Number.parseFloat(match![1]);
|
|
133
|
-
// Clamped to the area's bottom edge, not the 500px overdraw.
|
|
134
|
-
expect(y).toBeLessThanOrEqual(ydi.area.y + ydi.area.height + 0.01);
|
|
135
|
-
expect(y).toBeGreaterThanOrEqual(ydi.area.y);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('reveals via the button and reports the guess to onReveal in data coordinates', () => {
|
|
139
|
-
const onReveal = vi.fn();
|
|
140
|
-
chart = createChart(container, makeSpec(), { onReveal });
|
|
141
|
-
const samples = chart.layout.youDrawIt!.samples;
|
|
142
|
-
const inv = chart.layout.youDrawIt!.yInvert!;
|
|
143
|
-
const rect = overlay(container)!;
|
|
144
|
-
|
|
145
|
-
// Draw at the top pixel of the area at the first sample.
|
|
146
|
-
drawAt(rect, samples[0].px, inv.topPixel);
|
|
147
|
-
releaseMouse();
|
|
148
|
-
|
|
149
|
-
const button = container.querySelector('.oc-ydi-reveal-button') as HTMLButtonElement;
|
|
150
|
-
button.click();
|
|
151
|
-
|
|
152
|
-
expect(onReveal).toHaveBeenCalledTimes(1);
|
|
153
|
-
const guess = onReveal.mock.calls[0][0] as Array<{ x: string | number; y: number }>;
|
|
154
|
-
expect(guess.length).toBe(1);
|
|
155
|
-
expect(guess[0].x).toBe(samples[0].xValue);
|
|
156
|
-
// y maps back to the data value at the top pixel.
|
|
157
|
-
expect(guess[0].y).toBeCloseTo(inv.topData, 5);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('reveal keeps the guess visible and expands the clip to the full area', () => {
|
|
161
|
-
chart = createChart(container, makeSpec());
|
|
162
|
-
const ydi = chart.layout.youDrawIt!;
|
|
163
|
-
const rect = overlay(container)!;
|
|
164
|
-
drawAt(rect, ydi.samples[0].px, 130);
|
|
165
|
-
releaseMouse();
|
|
166
|
-
|
|
167
|
-
const before = clipRect(container)!;
|
|
168
|
-
// Pre-reveal: clip only covers up to `from`.
|
|
169
|
-
expect(Number.parseFloat(before.getAttribute('width') ?? '0')).toBeCloseTo(
|
|
170
|
-
Math.max(0, ydi.fromX - ydi.area.x),
|
|
171
|
-
1,
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
chart.revealDrawing();
|
|
175
|
-
|
|
176
|
-
// The clip now spans the whole area (real line fully revealed).
|
|
177
|
-
expect(Number.parseFloat(clipRect(container)!.getAttribute('width') ?? '0')).toBeCloseTo(
|
|
178
|
-
ydi.area.width,
|
|
179
|
-
1,
|
|
180
|
-
);
|
|
181
|
-
// The reader's guess path is still present for comparison.
|
|
182
|
-
const d = guessPath(container)!.getAttribute('d') ?? '';
|
|
183
|
-
expect(d.length).toBeGreaterThan(0);
|
|
184
|
-
// Drawing overlay is torn down after reveal.
|
|
185
|
-
expect(overlay(container)).toBeNull();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('skip-to-reveal works before any drawing (empty guess payload)', () => {
|
|
189
|
-
const onReveal = vi.fn();
|
|
190
|
-
chart = createChart(container, makeSpec(), { onReveal });
|
|
191
|
-
const button = container.querySelector('.oc-ydi-reveal-button') as HTMLButtonElement;
|
|
192
|
-
button.click();
|
|
193
|
-
expect(onReveal).toHaveBeenCalledWith([]);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('resetDrawing clears the guess and restores the drawing state', () => {
|
|
197
|
-
chart = createChart(container, makeSpec());
|
|
198
|
-
const samples = chart.layout.youDrawIt!.samples;
|
|
199
|
-
drawAt(overlay(container)!, samples[0].px, 140);
|
|
200
|
-
releaseMouse();
|
|
201
|
-
chart.revealDrawing();
|
|
202
|
-
expect(overlay(container)).toBeNull();
|
|
203
|
-
|
|
204
|
-
chart.resetDrawing();
|
|
205
|
-
|
|
206
|
-
// Guess cleared, overlay + prompt back, button re-enabled.
|
|
207
|
-
expect(guessPath(container)!.getAttribute('d')).toBe('');
|
|
208
|
-
expect(overlay(container)).not.toBeNull();
|
|
209
|
-
const button = container.querySelector('.oc-ydi-reveal-button') as HTMLButtonElement;
|
|
210
|
-
expect(button.disabled).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('supports touch drawing (touchstart/touchmove)', () => {
|
|
214
|
-
chart = createChart(container, makeSpec());
|
|
215
|
-
const samples = chart.layout.youDrawIt!.samples;
|
|
216
|
-
const rect = overlay(container)!;
|
|
217
|
-
|
|
218
|
-
const touch = (x: number, y: number) => ({ clientX: x, clientY: y }) as Touch;
|
|
219
|
-
rect.dispatchEvent(
|
|
220
|
-
new TouchEvent('touchstart', {
|
|
221
|
-
bubbles: true,
|
|
222
|
-
cancelable: true,
|
|
223
|
-
touches: [touch(samples[0].px, 120)],
|
|
224
|
-
}),
|
|
225
|
-
);
|
|
226
|
-
rect.dispatchEvent(
|
|
227
|
-
new TouchEvent('touchmove', {
|
|
228
|
-
bubbles: true,
|
|
229
|
-
cancelable: true,
|
|
230
|
-
touches: [touch(samples[1].px, 150)],
|
|
231
|
-
}),
|
|
232
|
-
);
|
|
233
|
-
rect.dispatchEvent(new TouchEvent('touchend', { bubbles: true }));
|
|
234
|
-
|
|
235
|
-
const commands = (guessPath(container)!.getAttribute('d') ?? '').match(/[ML]/g) ?? [];
|
|
236
|
-
expect(commands.length).toBe(2);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('reveals instantly under prefers-reduced-motion (no transition class)', () => {
|
|
240
|
-
vi.spyOn(window, 'matchMedia').mockImplementation(
|
|
241
|
-
(query: string) =>
|
|
242
|
-
({
|
|
243
|
-
matches: query.includes('reduce'),
|
|
244
|
-
media: query,
|
|
245
|
-
addEventListener: () => {},
|
|
246
|
-
removeEventListener: () => {},
|
|
247
|
-
addListener: () => {},
|
|
248
|
-
removeListener: () => {},
|
|
249
|
-
onchange: null,
|
|
250
|
-
dispatchEvent: () => false,
|
|
251
|
-
}) as unknown as MediaQueryList,
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
chart = createChart(container, makeSpec());
|
|
255
|
-
const ydi = chart.layout.youDrawIt!;
|
|
256
|
-
chart.revealDrawing();
|
|
257
|
-
|
|
258
|
-
const clip = clipRect(container)!;
|
|
259
|
-
// No animation class; width snaps straight to full.
|
|
260
|
-
expect(clip.classList.contains('oc-ydi-clip-animate')).toBe(false);
|
|
261
|
-
expect(Number.parseFloat(clip.getAttribute('width') ?? '0')).toBeCloseTo(ydi.area.width, 1);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('disables youDrawIt when edit callbacks are provided (mutually exclusive)', () => {
|
|
265
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
266
|
-
chart = createChart(container, makeSpec(), { onEdit: () => {} });
|
|
267
|
-
|
|
268
|
-
// No drawing overlay: edit mode wins, youDrawIt is suppressed.
|
|
269
|
-
expect(overlay(container)).toBeNull();
|
|
270
|
-
expect(container.querySelector('.oc-ydi-region')).toBeNull();
|
|
271
|
-
expect(warn).toHaveBeenCalledWith(expect.stringContaining('mutually exclusive'));
|
|
272
|
-
});
|
|
273
|
-
});
|
package/src/animation-vars.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared animation CSS custom-property stamping for vanilla renderers.
|
|
3
|
-
*
|
|
4
|
-
* Every renderer (svg, table, tilemap, sankey, map, barlist) sets the same
|
|
5
|
-
* family of `--oc-animation-*` custom properties before the element enters
|
|
6
|
-
* the DOM. This centralizes that stamping so each renderer only computes
|
|
7
|
-
* its own timing values and delegates the DOM writes here.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** CSS easing preset map for inline style custom properties. */
|
|
11
|
-
export const EASE_VAR_MAP: Record<string, string> = {
|
|
12
|
-
smooth: 'var(--oc-ease-smooth)',
|
|
13
|
-
snappy: 'var(--oc-ease-snappy)',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export interface AnimationVars {
|
|
17
|
-
/** Entrance animation duration, in ms. */
|
|
18
|
-
duration: number;
|
|
19
|
-
/** Per-item stagger delay, in ms. May be fractional (map renderer) — do NOT round. */
|
|
20
|
-
stagger: number;
|
|
21
|
-
/** Delay before annotations begin animating, in ms. */
|
|
22
|
-
annotationDelay?: number;
|
|
23
|
-
/** Easing preset name, mapped through EASE_VAR_MAP (defaults to smooth). */
|
|
24
|
-
ease?: string;
|
|
25
|
-
/** Per-segment duration for stacked bar sequencing, in ms (svg-renderer only). */
|
|
26
|
-
stackSegmentDuration?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Stamp animation CSS custom properties onto an element's inline style. */
|
|
30
|
-
export function stampAnimationVars(el: ElementCSSInlineStyle, v: AnimationVars): void {
|
|
31
|
-
el.style.setProperty('--oc-animation-duration', `${v.duration}ms`);
|
|
32
|
-
el.style.setProperty('--oc-animation-stagger', `${v.stagger}ms`);
|
|
33
|
-
if (v.annotationDelay !== undefined) {
|
|
34
|
-
el.style.setProperty('--oc-annotation-delay', `${v.annotationDelay}ms`);
|
|
35
|
-
}
|
|
36
|
-
if (v.ease !== undefined) {
|
|
37
|
-
el.style.setProperty('--oc-animation-ease', EASE_VAR_MAP[v.ease] || EASE_VAR_MAP.smooth);
|
|
38
|
-
}
|
|
39
|
-
if (v.stackSegmentDuration !== undefined) {
|
|
40
|
-
el.style.setProperty('--oc-stack-segment-duration', `${v.stackSegmentDuration}ms`);
|
|
41
|
-
}
|
|
42
|
-
}
|
package/src/animation.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation runtime for entrance animations.
|
|
3
|
-
*
|
|
4
|
-
* All animations are CSS-driven (keyframes + clip-path + transforms + opacity).
|
|
5
|
-
* This module handles lifecycle: cleanup after completion, cancellation on update.
|
|
6
|
-
* No WAAPI needed since clip-path handles line/area drawing.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Cancel entrance animations and clean up.
|
|
11
|
-
* Called when update() is invoked during animation, or on destroy.
|
|
12
|
-
*/
|
|
13
|
-
export function cancelAnimations(svg: SVGElement | null): void {
|
|
14
|
-
if (svg) {
|
|
15
|
-
svg.classList.remove('oc-animate');
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Set up animation cleanup that removes oc-animate after all animations complete.
|
|
21
|
-
*
|
|
22
|
-
* Uses the computed total animation time (duration + stagger * elementCount + annotation delay)
|
|
23
|
-
* rather than animationend events, because animationend fires per-element and the first
|
|
24
|
-
* element to finish would prematurely kill staggered animations still in progress.
|
|
25
|
-
*/
|
|
26
|
-
export function setupAnimationCleanup(svg: SVGElement, onComplete?: () => void): () => void {
|
|
27
|
-
// Read the animation timing from the CSS custom properties set by the renderer
|
|
28
|
-
const style = svg.style;
|
|
29
|
-
const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 600;
|
|
30
|
-
const stagger = parseFloat(style.getPropertyValue('--oc-animation-stagger')) || 0;
|
|
31
|
-
const annotationDelay = parseFloat(style.getPropertyValue('--oc-annotation-delay')) || 200;
|
|
32
|
-
|
|
33
|
-
// Count animated elements to compute total stagger span
|
|
34
|
-
const animatedElements = svg.querySelectorAll('[data-animation-index]').length;
|
|
35
|
-
const totalStagger = stagger * Math.max(0, animatedElements - 1);
|
|
36
|
-
|
|
37
|
-
// Total time: last element's stagger delay + its duration + annotation delay + buffer
|
|
38
|
-
const totalTime = totalStagger + duration + annotationDelay + 500;
|
|
39
|
-
|
|
40
|
-
const timer = setTimeout(() => {
|
|
41
|
-
svg.classList.remove('oc-animate');
|
|
42
|
-
onComplete?.();
|
|
43
|
-
}, totalTime);
|
|
44
|
-
|
|
45
|
-
return () => {
|
|
46
|
-
clearTimeout(timer);
|
|
47
|
-
cancelAnimations(svg);
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Set up animation cleanup for table entrance animations.
|
|
53
|
-
*
|
|
54
|
-
* Same timeout-based approach as chart animations: compute total time from
|
|
55
|
-
* CSS custom properties and row count, then remove oc-animate after completion.
|
|
56
|
-
*/
|
|
57
|
-
export function setupTableAnimationCleanup(wrapper: HTMLElement): () => void {
|
|
58
|
-
const style = wrapper.style;
|
|
59
|
-
const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 500;
|
|
60
|
-
const stagger = parseFloat(style.getPropertyValue('--oc-animation-stagger')) || 0;
|
|
61
|
-
|
|
62
|
-
const rows = wrapper.querySelectorAll('tbody tr').length;
|
|
63
|
-
const totalStagger = stagger * Math.max(0, rows - 1);
|
|
64
|
-
|
|
65
|
-
// Total: last row stagger + duration + buffer
|
|
66
|
-
const totalTime = totalStagger + duration + 300;
|
|
67
|
-
|
|
68
|
-
const timer = setTimeout(() => {
|
|
69
|
-
wrapper.classList.remove('oc-animate');
|
|
70
|
-
}, totalTime);
|
|
71
|
-
|
|
72
|
-
return () => {
|
|
73
|
-
clearTimeout(timer);
|
|
74
|
-
wrapper.classList.remove('oc-animate');
|
|
75
|
-
};
|
|
76
|
-
}
|