@opendata-ai/openchart-vanilla 6.5.1 → 6.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-vanilla",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@floating-ui/dom": "^1.7.6",
53
- "@opendata-ai/openchart-core": "6.5.1",
54
- "@opendata-ai/openchart-engine": "6.5.1",
53
+ "@opendata-ai/openchart-core": "6.5.2",
54
+ "@opendata-ai/openchart-engine": "6.5.2",
55
55
  "d3-force": "^3.0.0",
56
56
  "d3-quadtree": "^3.0.1"
57
57
  },
package/src/animation.ts CHANGED
@@ -23,7 +23,7 @@ export function cancelAnimations(svg: SVGElement | null): void {
23
23
  * rather than animationend events, because animationend fires per-element and the first
24
24
  * element to finish would prematurely kill staggered animations still in progress.
25
25
  */
26
- export function setupAnimationCleanup(svg: SVGElement): () => void {
26
+ export function setupAnimationCleanup(svg: SVGElement, onComplete?: () => void): () => void {
27
27
  // Read the animation timing from the CSS custom properties set by the renderer
28
28
  const style = svg.style;
29
29
  const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 600;
@@ -39,6 +39,7 @@ export function setupAnimationCleanup(svg: SVGElement): () => void {
39
39
 
40
40
  const timer = setTimeout(() => {
41
41
  svg.classList.remove('oc-animate');
42
+ onComplete?.();
42
43
  }, totalTime);
43
44
 
44
45
  return () => {
package/src/mount.ts CHANGED
@@ -1801,6 +1801,7 @@ export function createChart(
1801
1801
  // Animation state
1802
1802
  let isFirstRender = true;
1803
1803
  let cleanupAnimations: (() => void) | null = null;
1804
+ let pendingResize = false;
1804
1805
 
1805
1806
  // Selection and text editing state
1806
1807
  let selectedElement: ElementRef | null = options?.selectedElement ?? null;
@@ -2307,8 +2308,17 @@ export function createChart(
2307
2308
  }
2308
2309
 
2309
2310
  // Set up animation cleanup on first render only.
2311
+ // onComplete fires when animations finish naturally (not on cancellation/destroy).
2312
+ // It nulls out cleanupAnimations so resizes work after the animation window,
2313
+ // and replays any resize that was skipped mid-animation.
2310
2314
  if (shouldAnimate && svgElement) {
2311
- cleanupAnimations = setupAnimationCleanup(svgElement);
2315
+ cleanupAnimations = setupAnimationCleanup(svgElement, () => {
2316
+ cleanupAnimations = null;
2317
+ if (pendingResize) {
2318
+ pendingResize = false;
2319
+ resize();
2320
+ }
2321
+ });
2312
2322
  }
2313
2323
  if (isFirstRender) {
2314
2324
  isFirstRender = false;
@@ -2328,10 +2338,12 @@ export function createChart(
2328
2338
  if (destroyed) return;
2329
2339
  // Skip resize during entrance animation. The resize observer fires
2330
2340
  // immediately when the container first enters DOM layout, and re-rendering
2331
- // would destroy the animated SVG. This also blocks genuine resizes during
2332
- // the animation window (~1s), but catches up on the next resize event
2333
- // after the cleanup timeout fires.
2334
- if (cleanupAnimations) return;
2341
+ // would destroy the animated SVG. Resizes during this window are queued
2342
+ // and replayed once the animation completes via the onComplete callback.
2343
+ if (cleanupAnimations) {
2344
+ pendingResize = true;
2345
+ return;
2346
+ }
2335
2347
  render();
2336
2348
  }
2337
2349
 
@@ -2370,10 +2382,11 @@ export function createChart(
2370
2382
  if (destroyed) return;
2371
2383
  destroyed = true;
2372
2384
 
2373
- // Cancel entrance animations
2385
+ // Cancel entrance animations (cancellation does not fire onComplete)
2374
2386
  if (cleanupAnimations) {
2375
2387
  cleanupAnimations();
2376
2388
  cleanupAnimations = null;
2389
+ pendingResize = false;
2377
2390
  }
2378
2391
  cancelAnimations(svgElement);
2379
2392
 
@@ -901,38 +901,7 @@ function renderAnnotation(parent: SVGElement, annotation: ResolvedAnnotation, in
901
901
  // Render connector first (behind the label text)
902
902
  if (annotation.label.connector) {
903
903
  const c = annotation.label.connector;
904
- if (c.style === 'caret') {
905
- // Small directional chevron centered in the gap between the label
906
- // text and the data mark, pointing toward the data.
907
- const pointsDown = c.to.y > c.from.y;
908
- const caretSize = 4;
909
- // c.from.y is near the text baseline, not the visual bottom.
910
- // Estimate the text bottom from the label's line count and font size.
911
- const labelLines = annotation.label.text.split('\n');
912
- const labelFontSize = annotation.label.style.fontSize ?? 12;
913
- const labelLineHeight = labelFontSize * (annotation.label.style.lineHeight ?? 1.3);
914
- const textBottom =
915
- annotation.label.y + (labelLines.length - 1) * labelLineHeight + labelFontSize * 0.25;
916
- const textTop = annotation.label.y - labelFontSize;
917
- // Center caret in the gap between text edge and data point
918
- const gapEdge = pointsDown ? textBottom : textTop;
919
- const midY = (gapEdge + c.to.y) / 2;
920
- const tipX = c.to.x;
921
- const tipY = pointsDown ? midY + caretSize / 2 : midY - caretSize / 2;
922
- const baseY = pointsDown ? tipY - caretSize : tipY + caretSize;
923
- const path = createSVGElement('path');
924
- path.setAttribute('class', 'oc-annotation-connector');
925
- setAttrs(path, {
926
- d: `M${tipX - caretSize},${baseY} L${tipX},${tipY} L${tipX + caretSize},${baseY}`,
927
- fill: 'none',
928
- stroke: c.stroke,
929
- 'stroke-width': 1.5,
930
- 'stroke-opacity': 0.4,
931
- 'stroke-linecap': 'round',
932
- 'stroke-linejoin': 'round',
933
- });
934
- g.appendChild(path);
935
- } else if (c.style === 'curve') {
904
+ if (c.style === 'curve') {
936
905
  renderCurvedArrow(g, c.from, c.to, c.stroke);
937
906
  } else {
938
907
  const connector = createSVGElement('line');