@opendata-ai/openchart-vanilla 8.0.0-rc.9 → 8.0.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.
Files changed (150) hide show
  1. package/dist/{chunk-SPILUE5S.js → chunk-2NAQRP66.js} +2 -2
  2. package/dist/{chunk-IZQBT6QM.js → chunk-6BUCYCLD.js} +7283 -5881
  3. package/dist/chunk-6BUCYCLD.js.map +1 -0
  4. package/dist/chunk-DCODCLWI.js +234 -0
  5. package/dist/chunk-DCODCLWI.js.map +1 -0
  6. package/dist/{chunk-QW7ZB6NE.js → chunk-EKAUQAAX.js} +49 -45
  7. package/dist/chunk-EKAUQAAX.js.map +1 -0
  8. package/dist/chunk-RM5XS6NR.js +113 -0
  9. package/dist/chunk-RM5XS6NR.js.map +1 -0
  10. package/dist/chunk-T32UHMZA.js +282 -0
  11. package/dist/chunk-T32UHMZA.js.map +1 -0
  12. package/dist/export-gif.d.ts +113 -0
  13. package/dist/export-gif.js +21 -0
  14. package/dist/export-gif.js.map +1 -0
  15. package/dist/index.d.ts +309 -27
  16. package/dist/index.js +2020 -836
  17. package/dist/index.js.map +1 -1
  18. package/dist/{mount-CUKQLeqD.d.ts → mount-CL-nA0f-.d.ts} +85 -2
  19. package/dist/simulation-worker.js +83 -2
  20. package/dist/static.d.ts +2 -0
  21. package/dist/static.js +8 -69
  22. package/dist/static.js.map +1 -1
  23. package/dist/story/index.d.ts +25 -9
  24. package/dist/story/index.js +9 -7
  25. package/dist/story/index.js.map +1 -1
  26. package/dist/styles.css +1 -1
  27. package/dist/styles.css.map +1 -1
  28. package/package.json +14 -6
  29. package/dist/chunk-IZQBT6QM.js.map +0 -1
  30. package/dist/chunk-QW7ZB6NE.js.map +0 -1
  31. package/src/__test-fixtures__/dom.ts +0 -70
  32. package/src/__test-fixtures__/specs.ts +0 -214
  33. package/src/__tests__/animation.test.ts +0 -358
  34. package/src/__tests__/annotations-render.test.ts +0 -201
  35. package/src/__tests__/compound-labels.test.ts +0 -122
  36. package/src/__tests__/continuous-legend.test.ts +0 -91
  37. package/src/__tests__/crosshair.test.ts +0 -130
  38. package/src/__tests__/edit-events.test.ts +0 -1477
  39. package/src/__tests__/editable-prop.test.ts +0 -174
  40. package/src/__tests__/events.test.ts +0 -387
  41. package/src/__tests__/export.test.ts +0 -241
  42. package/src/__tests__/font-race.test.ts +0 -199
  43. package/src/__tests__/gradient-ids.test.ts +0 -159
  44. package/src/__tests__/gradient-utils.test.ts +0 -125
  45. package/src/__tests__/invert.test.ts +0 -187
  46. package/src/__tests__/known-bugs-render.test.ts +0 -183
  47. package/src/__tests__/map.test.ts +0 -636
  48. package/src/__tests__/mount.test.ts +0 -357
  49. package/src/__tests__/pattern-fills.test.ts +0 -105
  50. package/src/__tests__/resize-timing.test.ts +0 -218
  51. package/src/__tests__/sankey.test.ts +0 -133
  52. package/src/__tests__/selection-events.test.ts +0 -1062
  53. package/src/__tests__/series-search.test.ts +0 -311
  54. package/src/__tests__/static.test.ts +0 -141
  55. package/src/__tests__/svg-renderer.test.ts +0 -895
  56. package/src/__tests__/table-keyboard.test.ts +0 -361
  57. package/src/__tests__/table-mount.test.ts +0 -639
  58. package/src/__tests__/tilemap.test.ts +0 -158
  59. package/src/__tests__/tooltip.test.ts +0 -337
  60. package/src/__tests__/transition.test.ts +0 -1792
  61. package/src/__tests__/update-scrolly-matrix.test.ts +0 -563
  62. package/src/__tests__/you-draw-it.test.ts +0 -273
  63. package/src/animation-vars.ts +0 -42
  64. package/src/animation.ts +0 -76
  65. package/src/barlist-mount.ts +0 -383
  66. package/src/barlist-renderer.ts +0 -267
  67. package/src/dom-helpers.ts +0 -17
  68. package/src/export.ts +0 -482
  69. package/src/gradient-utils.ts +0 -138
  70. package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
  71. package/src/graph/__tests__/graph-mount.test.ts +0 -213
  72. package/src/graph/__tests__/interaction.test.ts +0 -205
  73. package/src/graph/__tests__/keyboard.test.ts +0 -653
  74. package/src/graph/__tests__/search.test.ts +0 -88
  75. package/src/graph/__tests__/simulation.test.ts +0 -233
  76. package/src/graph/__tests__/spatial-index.test.ts +0 -142
  77. package/src/graph/__tests__/zoom.test.ts +0 -203
  78. package/src/graph/canvas-renderer.ts +0 -747
  79. package/src/graph/interaction.ts +0 -367
  80. package/src/graph/keyboard.ts +0 -208
  81. package/src/graph/search.ts +0 -50
  82. package/src/graph/simulation-worker-url.ts +0 -12
  83. package/src/graph/simulation-worker.ts +0 -278
  84. package/src/graph/simulation.ts +0 -422
  85. package/src/graph/spatial-index.ts +0 -121
  86. package/src/graph/types.ts +0 -47
  87. package/src/graph/worker-protocol.ts +0 -70
  88. package/src/graph/zoom.ts +0 -134
  89. package/src/graph-mount.ts +0 -888
  90. package/src/index.ts +0 -76
  91. package/src/interactions/chart-events.ts +0 -139
  92. package/src/interactions/crosshair.ts +0 -233
  93. package/src/interactions/drag-handler.ts +0 -175
  94. package/src/interactions/editing-drags.ts +0 -649
  95. package/src/interactions/index.ts +0 -27
  96. package/src/interactions/invert.ts +0 -54
  97. package/src/interactions/keyboard-nav.ts +0 -111
  98. package/src/interactions/legend-interaction.ts +0 -46
  99. package/src/interactions/selection.ts +0 -286
  100. package/src/interactions/tooltip-events.ts +0 -72
  101. package/src/map-camera.ts +0 -111
  102. package/src/map-mount.ts +0 -797
  103. package/src/map-renderer.ts +0 -476
  104. package/src/map-transition.ts +0 -99
  105. package/src/measure-text.ts +0 -134
  106. package/src/mount.ts +0 -1434
  107. package/src/pattern-utils.ts +0 -110
  108. package/src/renderers/annotations.ts +0 -388
  109. package/src/renderers/axes.ts +0 -351
  110. package/src/renderers/brand.ts +0 -87
  111. package/src/renderers/chrome.ts +0 -196
  112. package/src/renderers/endpoint-labels.ts +0 -150
  113. package/src/renderers/legend.ts +0 -312
  114. package/src/renderers/marks.ts +0 -561
  115. package/src/renderers/metrics.ts +0 -63
  116. package/src/renderers/svg-dom.ts +0 -40
  117. package/src/renderers/table-cells.ts +0 -446
  118. package/src/resize-observer.ts +0 -46
  119. package/src/sankey-mount.ts +0 -622
  120. package/src/sankey-renderer.ts +0 -614
  121. package/src/series-search.ts +0 -392
  122. package/src/static.ts +0 -251
  123. package/src/story/__tests__/camera-math.test.ts +0 -145
  124. package/src/story/__tests__/create-chart-story.test.ts +0 -317
  125. package/src/story/__tests__/progress-math.test.ts +0 -95
  126. package/src/story/__tests__/scroll-driver.test.ts +0 -130
  127. package/src/story/__tests__/tween.test.ts +0 -159
  128. package/src/story/camera-math.ts +0 -125
  129. package/src/story/create-chart-story.ts +0 -353
  130. package/src/story/crossfade.ts +0 -70
  131. package/src/story/index.ts +0 -39
  132. package/src/story/progress-math.ts +0 -85
  133. package/src/story/resolve-camera-target.ts +0 -119
  134. package/src/story/scroll-driver.ts +0 -206
  135. package/src/story/story-camera.ts +0 -49
  136. package/src/story/tween.ts +0 -112
  137. package/src/story/types.ts +0 -76
  138. package/src/svg-ids.ts +0 -23
  139. package/src/svg-renderer.ts +0 -383
  140. package/src/table-keyboard.ts +0 -266
  141. package/src/table-mount.ts +0 -567
  142. package/src/table-renderer.ts +0 -414
  143. package/src/text-edit-overlay.ts +0 -389
  144. package/src/theme-tokens.ts +0 -51
  145. package/src/tilemap-mount.ts +0 -511
  146. package/src/tilemap-renderer.ts +0 -396
  147. package/src/tooltip.ts +0 -177
  148. package/src/transition.ts +0 -2816
  149. package/src/you-draw-it.ts +0 -480
  150. /package/dist/{chunk-SPILUE5S.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
- });
@@ -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
- }