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