@opendata-ai/openchart-vanilla 6.3.0 → 6.5.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.
package/dist/index.js CHANGED
@@ -2561,7 +2561,7 @@ import { computePosition, flip, offset, shift } from "@floating-ui/dom";
2561
2561
  var TOOLTIP_OFFSET = 12;
2562
2562
  function createTooltipManager(container) {
2563
2563
  const tooltip = document.createElement("div");
2564
- tooltip.className = "viz-tooltip";
2564
+ tooltip.className = "oc-tooltip";
2565
2565
  tooltip.setAttribute("role", "tooltip");
2566
2566
  container.style.position = container.style.position || "relative";
2567
2567
  container.appendChild(tooltip);
@@ -2580,19 +2580,19 @@ function createTooltipManager(container) {
2580
2580
  let html = "";
2581
2581
  if (content.title) {
2582
2582
  const titleColor = content.fields.find((f) => f.color)?.color;
2583
- html += '<div class="viz-tooltip-header">';
2583
+ html += '<div class="oc-tooltip-header">';
2584
2584
  if (titleColor) {
2585
- html += `<span class="viz-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
2585
+ html += `<span class="oc-tooltip-dot" style="background:${esc(titleColor)}"></span>`;
2586
2586
  }
2587
- html += `<span class="viz-tooltip-title">${esc(content.title)}</span>`;
2587
+ html += `<span class="oc-tooltip-title">${esc(content.title)}</span>`;
2588
2588
  html += "</div>";
2589
2589
  }
2590
2590
  if (content.fields.length > 0) {
2591
- html += '<div class="viz-tooltip-body">';
2591
+ html += '<div class="oc-tooltip-body">';
2592
2592
  for (const field of content.fields) {
2593
- html += '<div class="viz-tooltip-row">';
2594
- html += `<span class="viz-tooltip-label">${esc(field.label)}</span>`;
2595
- html += `<span class="viz-tooltip-value">${esc(field.value)}</span>`;
2593
+ html += '<div class="oc-tooltip-row">';
2594
+ html += `<span class="oc-tooltip-label">${esc(field.label)}</span>`;
2595
+ html += `<span class="oc-tooltip-value">${esc(field.value)}</span>`;
2596
2596
  html += "</div>";
2597
2597
  }
2598
2598
  html += "</div>";
@@ -2771,27 +2771,27 @@ function createGraph(container, spec, options) {
2771
2771
  const { width, height } = getContainerDimensions();
2772
2772
  const isDark = resolveDarkMode(options?.darkMode);
2773
2773
  wrapper = document.createElement("div");
2774
- wrapper.className = isDark ? "viz-graph-wrapper viz-dark" : "viz-graph-wrapper";
2774
+ wrapper.className = isDark ? "oc-graph-wrapper oc-dark" : "oc-graph-wrapper";
2775
2775
  if (isDark) {
2776
- container.classList.add("viz-dark");
2776
+ container.classList.add("oc-dark");
2777
2777
  } else {
2778
- container.classList.remove("viz-dark");
2778
+ container.classList.remove("oc-dark");
2779
2779
  }
2780
2780
  const resolvedTheme = compilation.theme;
2781
2781
  if (resolvedTheme) {
2782
2782
  const s = wrapper.style;
2783
- s.setProperty("--viz-bg", resolvedTheme.colors.background);
2784
- s.setProperty("--viz-text", resolvedTheme.colors.text);
2785
- s.setProperty("--viz-text-secondary", resolvedTheme.colors.axis ?? resolvedTheme.colors.text);
2786
- s.setProperty("--viz-font-family", resolvedTheme.fonts.family);
2783
+ s.setProperty("--oc-bg", resolvedTheme.colors.background);
2784
+ s.setProperty("--oc-text", resolvedTheme.colors.text);
2785
+ s.setProperty("--oc-text-secondary", resolvedTheme.colors.axis ?? resolvedTheme.colors.text);
2786
+ s.setProperty("--oc-font-family", resolvedTheme.fonts.family);
2787
2787
  s.fontFamily = resolvedTheme.fonts.family;
2788
2788
  }
2789
2789
  chromeEl = document.createElement("div");
2790
- chromeEl.className = "viz-graph-chrome";
2790
+ chromeEl.className = "oc-graph-chrome";
2791
2791
  renderChrome2();
2792
2792
  wrapper.appendChild(chromeEl);
2793
2793
  canvas = document.createElement("canvas");
2794
- canvas.className = "viz-graph-canvas";
2794
+ canvas.className = "oc-graph-canvas";
2795
2795
  canvas.setAttribute("role", "img");
2796
2796
  if (compilation.a11y?.altText) {
2797
2797
  canvas.setAttribute("aria-label", compilation.a11y.altText);
@@ -2799,7 +2799,7 @@ function createGraph(container, spec, options) {
2799
2799
  wrapper.appendChild(canvas);
2800
2800
  if (options?.legend !== false) {
2801
2801
  legendEl = document.createElement("div");
2802
- legendEl.className = "viz-graph-legend";
2802
+ legendEl.className = "oc-graph-legend";
2803
2803
  renderLegend2();
2804
2804
  wrapper.appendChild(legendEl);
2805
2805
  }
@@ -2813,10 +2813,10 @@ function createGraph(container, spec, options) {
2813
2813
  if (!chromeEl) return;
2814
2814
  let html = "";
2815
2815
  if (compilation.chrome.title) {
2816
- html += `<h2 class="viz-title">${escapeHtml(compilation.chrome.title.text)}</h2>`;
2816
+ html += `<h2 class="oc-title">${escapeHtml(compilation.chrome.title.text)}</h2>`;
2817
2817
  }
2818
2818
  if (compilation.chrome.subtitle) {
2819
- html += `<p class="viz-subtitle">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;
2819
+ html += `<p class="oc-subtitle">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;
2820
2820
  }
2821
2821
  chromeEl.innerHTML = html;
2822
2822
  if (!html) {
@@ -2835,8 +2835,8 @@ function createGraph(container, spec, options) {
2835
2835
  legendEl.style.display = "";
2836
2836
  let html = "";
2837
2837
  for (const entry of entries) {
2838
- html += '<div class="viz-graph-legend-item">';
2839
- html += `<span class="viz-graph-legend-swatch" style="background:${escapeHtml(entry.color)}"></span>`;
2838
+ html += '<div class="oc-graph-legend-item">';
2839
+ html += `<span class="oc-graph-legend-swatch" style="background:${escapeHtml(entry.color)}"></span>`;
2840
2840
  html += `<span>${escapeHtml(entry.label)}</span>`;
2841
2841
  html += "</div>";
2842
2842
  }
@@ -3017,14 +3017,14 @@ function createGraph(container, spec, options) {
3017
3017
  const x3 = node?.x ?? 0;
3018
3018
  const y3 = node?.y ?? 0;
3019
3019
  simulation?.pinNode(nodeId, x3, y3);
3020
- canvas?.classList.add("viz-graph-canvas--dragging");
3020
+ canvas?.classList.add("oc-graph-canvas--dragging");
3021
3021
  },
3022
3022
  onNodeDrag(nodeId, x3, y3) {
3023
3023
  simulation?.dragNode(nodeId, x3, y3);
3024
3024
  },
3025
3025
  onNodeDragEnd(nodeId) {
3026
3026
  simulation?.unpinNode(nodeId);
3027
- canvas?.classList.remove("viz-graph-canvas--dragging");
3027
+ canvas?.classList.remove("oc-graph-canvas--dragging");
3028
3028
  },
3029
3029
  onDoubleClick(nodeId) {
3030
3030
  options?.onNodeDoubleClick?.(nodeDataById(nodeId));
@@ -3199,7 +3199,7 @@ function createGraph(container, spec, options) {
3199
3199
  chromeEl = null;
3200
3200
  legendEl = null;
3201
3201
  renderer = null;
3202
- container.classList.remove("viz-dark");
3202
+ container.classList.remove("oc-dark");
3203
3203
  }
3204
3204
  try {
3205
3205
  compilation = compile();
@@ -3255,12 +3255,62 @@ function escapeHtml(str) {
3255
3255
  }
3256
3256
 
3257
3257
  // src/mount.ts
3258
- import { isLayerSpec } from "@opendata-ai/openchart-core";
3258
+ import { elementRef, isLayerSpec } from "@opendata-ai/openchart-core";
3259
3259
  import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
3260
3260
 
3261
+ // src/animation.ts
3262
+ function cancelAnimations(svg) {
3263
+ if (svg) {
3264
+ svg.classList.remove("oc-animate");
3265
+ }
3266
+ }
3267
+ function setupAnimationCleanup(svg) {
3268
+ const style = svg.style;
3269
+ const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 600;
3270
+ const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
3271
+ const annotationDelay = parseFloat(style.getPropertyValue("--oc-annotation-delay")) || 200;
3272
+ const animatedElements = svg.querySelectorAll("[data-animation-index]").length;
3273
+ const totalStagger = stagger * Math.max(0, animatedElements - 1);
3274
+ const totalTime = totalStagger + duration + annotationDelay + 500;
3275
+ const timer2 = setTimeout(() => {
3276
+ svg.classList.remove("oc-animate");
3277
+ }, totalTime);
3278
+ return () => {
3279
+ clearTimeout(timer2);
3280
+ cancelAnimations(svg);
3281
+ };
3282
+ }
3283
+ function setupTableAnimationCleanup(wrapper) {
3284
+ const style = wrapper.style;
3285
+ const duration = parseFloat(style.getPropertyValue("--oc-animation-duration")) || 500;
3286
+ const stagger = parseFloat(style.getPropertyValue("--oc-animation-stagger")) || 0;
3287
+ const rows = wrapper.querySelectorAll("tbody tr").length;
3288
+ const totalStagger = stagger * Math.max(0, rows - 1);
3289
+ const totalTime = totalStagger + duration + 300;
3290
+ const timer2 = setTimeout(() => {
3291
+ wrapper.classList.remove("oc-animate");
3292
+ }, totalTime);
3293
+ return () => {
3294
+ clearTimeout(timer2);
3295
+ wrapper.classList.remove("oc-animate");
3296
+ };
3297
+ }
3298
+
3261
3299
  // src/svg-renderer.ts
3262
3300
  import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2, estimateTextWidth } from "@opendata-ai/openchart-core";
3301
+ import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
3263
3302
  var SVG_NS = "http://www.w3.org/2000/svg";
3303
+ var currentAnimation;
3304
+ function stampAnimationAttrs(el, mark, fallbackIndex) {
3305
+ if (!currentAnimation?.enabled) return;
3306
+ const idx = mark.animationIndex ?? fallbackIndex;
3307
+ el.setAttribute("data-animation-index", String(idx));
3308
+ el.style.setProperty("--oc-mark-index", String(idx));
3309
+ }
3310
+ var EASE_VAR_MAP = {
3311
+ smooth: "var(--oc-ease-smooth)",
3312
+ snappy: "var(--oc-ease-snappy)"
3313
+ };
3264
3314
  function computeXAxisExtent(layout) {
3265
3315
  const xAxis = layout.axes.x;
3266
3316
  if (!xAxis) return 0;
@@ -3363,13 +3413,13 @@ function renderChromeElement(parent, element, className, chromeKey) {
3363
3413
  }
3364
3414
  function renderChrome(parent, layout) {
3365
3415
  const g = createSVGElement("g");
3366
- g.setAttribute("class", "viz-chrome");
3416
+ g.setAttribute("class", "oc-chrome");
3367
3417
  const { chrome } = layout;
3368
3418
  if (chrome.title) {
3369
- renderChromeElement(g, chrome.title, "viz-title", "title");
3419
+ renderChromeElement(g, chrome.title, "oc-title", "title");
3370
3420
  }
3371
3421
  if (chrome.subtitle) {
3372
- renderChromeElement(g, chrome.subtitle, "viz-subtitle", "subtitle");
3422
+ renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle");
3373
3423
  }
3374
3424
  const xAxisExtent = computeXAxisExtent(layout);
3375
3425
  const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
@@ -3377,7 +3427,7 @@ function renderChrome(parent, layout) {
3377
3427
  renderChromeElement(
3378
3428
  g,
3379
3429
  { ...chrome.source, y: bottomOffset + chrome.source.y },
3380
- "viz-source",
3430
+ "oc-source",
3381
3431
  "source"
3382
3432
  );
3383
3433
  }
@@ -3385,7 +3435,7 @@ function renderChrome(parent, layout) {
3385
3435
  renderChromeElement(
3386
3436
  g,
3387
3437
  { ...chrome.byline, y: bottomOffset + chrome.byline.y },
3388
- "viz-byline",
3438
+ "oc-byline",
3389
3439
  "byline"
3390
3440
  );
3391
3441
  }
@@ -3393,7 +3443,7 @@ function renderChrome(parent, layout) {
3393
3443
  renderChromeElement(
3394
3444
  g,
3395
3445
  { ...chrome.footer, y: bottomOffset + chrome.footer.y },
3396
- "viz-footer",
3446
+ "oc-footer",
3397
3447
  "footer"
3398
3448
  );
3399
3449
  }
@@ -3401,11 +3451,11 @@ function renderChrome(parent, layout) {
3401
3451
  }
3402
3452
  function renderAxis(parent, axis, orientation, layout) {
3403
3453
  const g = createSVGElement("g");
3404
- g.setAttribute("class", `viz-axis viz-axis-${orientation}`);
3454
+ g.setAttribute("class", `oc-axis oc-axis-${orientation}`);
3405
3455
  const { area } = layout;
3406
3456
  if (orientation === "x") {
3407
3457
  const line = createSVGElement("line");
3408
- line.setAttribute("class", "viz-axis-line");
3458
+ line.setAttribute("class", "oc-axis-line");
3409
3459
  setAttrs(line, {
3410
3460
  x1: axis.start.x,
3411
3461
  y1: axis.start.y,
@@ -3419,7 +3469,7 @@ function renderAxis(parent, axis, orientation, layout) {
3419
3469
  for (const tick of axis.ticks) {
3420
3470
  if (orientation === "x") {
3421
3471
  const label = createSVGElement("text");
3422
- label.setAttribute("class", "viz-axis-tick");
3472
+ label.setAttribute("class", "oc-axis-tick");
3423
3473
  if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
3424
3474
  const labelX = tick.position;
3425
3475
  const labelY = area.y + area.height + 6;
@@ -3442,7 +3492,7 @@ function renderAxis(parent, axis, orientation, layout) {
3442
3492
  g.appendChild(label);
3443
3493
  } else {
3444
3494
  const label = createSVGElement("text");
3445
- label.setAttribute("class", "viz-axis-tick");
3495
+ label.setAttribute("class", "oc-axis-tick");
3446
3496
  setAttrs(label, {
3447
3497
  x: area.x - 6,
3448
3498
  y: tick.position,
@@ -3456,7 +3506,7 @@ function renderAxis(parent, axis, orientation, layout) {
3456
3506
  }
3457
3507
  for (const gridline of axis.gridlines) {
3458
3508
  const gl = createSVGElement("line");
3459
- gl.setAttribute("class", "viz-gridline");
3509
+ gl.setAttribute("class", "oc-gridline");
3460
3510
  if (orientation === "y") {
3461
3511
  setAttrs(gl, {
3462
3512
  x1: area.x,
@@ -3482,7 +3532,7 @@ function renderAxis(parent, axis, orientation, layout) {
3482
3532
  }
3483
3533
  if (axis.label && axis.labelStyle) {
3484
3534
  const axisLabel = createSVGElement("text");
3485
- axisLabel.setAttribute("class", "viz-axis-title");
3535
+ axisLabel.setAttribute("class", "oc-axis-title");
3486
3536
  applyTextStyle(axisLabel, axis.labelStyle);
3487
3537
  axisLabel.textContent = axis.label;
3488
3538
  if (orientation === "x") {
@@ -3533,7 +3583,8 @@ function registerMarkRenderer(type, renderer) {
3533
3583
  function renderLineMark(mark, index2) {
3534
3584
  const g = createSVGElement("g");
3535
3585
  g.setAttribute("data-mark-id", `line-${mark.seriesKey ?? index2}`);
3536
- g.setAttribute("class", "viz-mark viz-mark-line");
3586
+ g.setAttribute("class", "oc-mark oc-mark-line");
3587
+ stampAnimationAttrs(g, mark, index2);
3537
3588
  if (mark.points.length > 1) {
3538
3589
  const path = createSVGElement("path");
3539
3590
  const d = mark.path ?? mark.points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
@@ -3553,7 +3604,7 @@ function renderLineMark(mark, index2) {
3553
3604
  }
3554
3605
  if (mark.label?.visible) {
3555
3606
  const label = createSVGElement("text");
3556
- label.setAttribute("class", "viz-mark-label");
3607
+ label.setAttribute("class", "oc-mark-label");
3557
3608
  if (mark.seriesKey) {
3558
3609
  label.setAttribute("data-series", mark.seriesKey);
3559
3610
  }
@@ -3563,7 +3614,7 @@ function renderLineMark(mark, index2) {
3563
3614
  g.appendChild(label);
3564
3615
  if (mark.label.connector) {
3565
3616
  const connector = createSVGElement("line");
3566
- connector.setAttribute("class", "viz-mark-connector");
3617
+ connector.setAttribute("class", "oc-mark-connector");
3567
3618
  setAttrs(connector, {
3568
3619
  x1: mark.label.connector.from.x,
3569
3620
  y1: mark.label.connector.from.y,
@@ -3581,7 +3632,8 @@ function renderLineMark(mark, index2) {
3581
3632
  function renderAreaMark(mark, index2) {
3582
3633
  const g = createSVGElement("g");
3583
3634
  g.setAttribute("data-mark-id", `area-${mark.seriesKey ?? index2}`);
3584
- g.setAttribute("class", "viz-mark viz-mark-area");
3635
+ g.setAttribute("class", "oc-mark oc-mark-area");
3636
+ stampAnimationAttrs(g, mark, index2);
3585
3637
  if (mark.path) {
3586
3638
  const fill = createSVGElement("path");
3587
3639
  setAttrs(fill, {
@@ -3593,6 +3645,7 @@ function renderAreaMark(mark, index2) {
3593
3645
  g.appendChild(fill);
3594
3646
  if (mark.stroke && mark.topPath) {
3595
3647
  const strokePath = createSVGElement("path");
3648
+ strokePath.setAttribute("class", "oc-area-top");
3596
3649
  setAttrs(strokePath, {
3597
3650
  d: mark.topPath,
3598
3651
  fill: "none",
@@ -3607,7 +3660,11 @@ function renderAreaMark(mark, index2) {
3607
3660
  function renderRectMark(mark, index2) {
3608
3661
  const g = createSVGElement("g");
3609
3662
  g.setAttribute("data-mark-id", `rect-${index2}`);
3610
- g.setAttribute("class", "viz-mark viz-mark-rect");
3663
+ g.setAttribute("class", "oc-mark oc-mark-rect");
3664
+ stampAnimationAttrs(g, mark, index2);
3665
+ if (currentAnimation?.enabled && mark.orient === "horizontal") {
3666
+ g.setAttribute("data-orient", "horizontal");
3667
+ }
3611
3668
  const rect = createSVGElement("rect");
3612
3669
  setAttrs(rect, {
3613
3670
  x: mark.x,
@@ -3628,7 +3685,7 @@ function renderRectMark(mark, index2) {
3628
3685
  g.appendChild(rect);
3629
3686
  if (mark.label?.visible) {
3630
3687
  const label = createSVGElement("text");
3631
- label.setAttribute("class", "viz-mark-label");
3688
+ label.setAttribute("class", "oc-mark-label");
3632
3689
  setAttrs(label, { x: mark.label.x, y: mark.label.y });
3633
3690
  applyTextStyle(label, mark.label.style);
3634
3691
  label.textContent = mark.label.text;
@@ -3639,8 +3696,9 @@ function renderRectMark(mark, index2) {
3639
3696
  function renderArcMark(mark, index2) {
3640
3697
  const g = createSVGElement("g");
3641
3698
  g.setAttribute("data-mark-id", `arc-${index2}`);
3642
- g.setAttribute("class", "viz-mark viz-mark-arc");
3699
+ g.setAttribute("class", "oc-mark oc-mark-arc");
3643
3700
  g.setAttribute("transform", `translate(${mark.center.x},${mark.center.y})`);
3701
+ stampAnimationAttrs(g, mark, index2);
3644
3702
  const path = createSVGElement("path");
3645
3703
  setAttrs(path, {
3646
3704
  d: mark.path,
@@ -3651,7 +3709,7 @@ function renderArcMark(mark, index2) {
3651
3709
  g.appendChild(path);
3652
3710
  if (mark.label?.visible) {
3653
3711
  const label = createSVGElement("text");
3654
- label.setAttribute("class", "viz-mark-label");
3712
+ label.setAttribute("class", "oc-mark-label");
3655
3713
  setAttrs(label, {
3656
3714
  x: mark.label.x - mark.center.x,
3657
3715
  y: mark.label.y - mark.center.y
@@ -3665,7 +3723,8 @@ function renderArcMark(mark, index2) {
3665
3723
  function renderPointMark(mark, index2) {
3666
3724
  const circle = createSVGElement("circle");
3667
3725
  circle.setAttribute("data-mark-id", `point-${index2}`);
3668
- circle.setAttribute("class", "viz-mark viz-mark-point");
3726
+ circle.setAttribute("class", "oc-mark oc-mark-point");
3727
+ stampAnimationAttrs(circle, mark, index2);
3669
3728
  setAttrs(circle, {
3670
3729
  cx: mark.cx,
3671
3730
  cy: mark.cy,
@@ -3682,7 +3741,8 @@ function renderPointMark(mark, index2) {
3682
3741
  function renderTextMark(mark, index2) {
3683
3742
  const text = createSVGElement("text");
3684
3743
  text.setAttribute("data-mark-id", `textMark-${index2}`);
3685
- text.setAttribute("class", "viz-mark viz-mark-text");
3744
+ text.setAttribute("class", "oc-mark oc-mark-text");
3745
+ stampAnimationAttrs(text, mark, index2);
3686
3746
  setAttrs(text, {
3687
3747
  x: mark.x,
3688
3748
  y: mark.y,
@@ -3705,7 +3765,8 @@ function renderTextMark(mark, index2) {
3705
3765
  function renderRuleMark(mark, index2) {
3706
3766
  const line = createSVGElement("line");
3707
3767
  line.setAttribute("data-mark-id", `rule-${index2}`);
3708
- line.setAttribute("class", "viz-mark viz-mark-rule");
3768
+ line.setAttribute("class", "oc-mark oc-mark-rule");
3769
+ stampAnimationAttrs(line, mark, index2);
3709
3770
  setAttrs(line, {
3710
3771
  x1: mark.x1,
3711
3772
  y1: mark.y1,
@@ -3725,7 +3786,8 @@ function renderRuleMark(mark, index2) {
3725
3786
  function renderTickMark(mark, index2) {
3726
3787
  const line = createSVGElement("line");
3727
3788
  line.setAttribute("data-mark-id", `tick-${index2}`);
3728
- line.setAttribute("class", "viz-mark viz-mark-tick");
3789
+ line.setAttribute("class", "oc-mark oc-mark-tick");
3790
+ stampAnimationAttrs(line, mark, index2);
3729
3791
  const half = mark.length / 2;
3730
3792
  if (mark.orient === "vertical") {
3731
3793
  setAttrs(line, {
@@ -3774,28 +3836,37 @@ function getMarkSeries(mark) {
3774
3836
  }
3775
3837
  function renderMarks(parent, layout) {
3776
3838
  const g = createSVGElement("g");
3777
- g.setAttribute("class", "viz-marks");
3839
+ g.setAttribute("class", "oc-marks");
3778
3840
  for (let i = 0; i < layout.marks.length; i++) {
3779
3841
  const mark = layout.marks[i];
3780
3842
  const renderer = markRenderers[mark.type];
3781
- if (renderer) {
3782
- const el = renderer(mark, i);
3783
- if (mark.aria?.label) {
3784
- el.setAttribute("aria-label", mark.aria.label);
3785
- }
3786
- const series = getMarkSeries(mark);
3787
- if (series) {
3788
- el.setAttribute("data-series", series);
3843
+ if (!renderer) continue;
3844
+ const el = renderer(mark, i);
3845
+ if (mark.aria?.label) {
3846
+ el.setAttribute("aria-label", mark.aria.label);
3847
+ }
3848
+ const series = getMarkSeries(mark);
3849
+ if (series) {
3850
+ el.setAttribute("data-series", series);
3851
+ }
3852
+ if (currentAnimation?.enabled && mark.type === "rect") {
3853
+ const rect = mark;
3854
+ if (rect.stackGroup && rect.stackPos !== void 0) {
3855
+ el.setAttribute("data-stack-pos", String(rect.stackPos));
3856
+ el.style.setProperty(
3857
+ "--oc-stack-pos",
3858
+ String(rect.stackPos)
3859
+ );
3789
3860
  }
3790
- g.appendChild(el);
3791
3861
  }
3862
+ g.appendChild(el);
3792
3863
  }
3793
3864
  parent.appendChild(g);
3794
3865
  }
3795
3866
  function renderAnnotations(parent, layout) {
3796
3867
  if (layout.annotations.length === 0) return;
3797
3868
  const g = createSVGElement("g");
3798
- g.setAttribute("class", "viz-annotations");
3869
+ g.setAttribute("class", "oc-annotations");
3799
3870
  for (let i = 0; i < layout.annotations.length; i++) {
3800
3871
  renderAnnotation(g, layout.annotations[i], i);
3801
3872
  }
@@ -3821,7 +3892,7 @@ function renderCurvedArrow(parent, from, to, stroke) {
3821
3892
  const baseX = to.x - ux * arrowLen;
3822
3893
  const baseY = tipY - uy * arrowLen;
3823
3894
  const path = createSVGElement("path");
3824
- path.setAttribute("class", "viz-annotation-connector");
3895
+ path.setAttribute("class", "oc-annotation-connector");
3825
3896
  setAttrs(path, {
3826
3897
  d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
3827
3898
  fill: "none",
@@ -3832,7 +3903,7 @@ function renderCurvedArrow(parent, from, to, stroke) {
3832
3903
  const px = -uy;
3833
3904
  const py = ux;
3834
3905
  const arrow = createSVGElement("polygon");
3835
- arrow.setAttribute("class", "viz-annotation-connector");
3906
+ arrow.setAttribute("class", "oc-annotation-connector");
3836
3907
  setAttrs(arrow, {
3837
3908
  points: [
3838
3909
  `${to.x},${tipY}`,
@@ -3845,11 +3916,14 @@ function renderCurvedArrow(parent, from, to, stroke) {
3845
3916
  }
3846
3917
  function renderAnnotation(parent, annotation, index2) {
3847
3918
  const g = createSVGElement("g");
3848
- g.setAttribute("class", `viz-annotation viz-annotation-${annotation.type}`);
3919
+ g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
3849
3920
  g.setAttribute("data-annotation-index", String(index2));
3921
+ if (annotation.id) {
3922
+ g.setAttribute("data-annotation-id", annotation.id);
3923
+ }
3850
3924
  if (annotation.rect) {
3851
3925
  const rect = createSVGElement("rect");
3852
- rect.setAttribute("class", "viz-annotation-range");
3926
+ rect.setAttribute("class", "oc-annotation-range");
3853
3927
  setAttrs(rect, {
3854
3928
  x: annotation.rect.x,
3855
3929
  y: annotation.rect.y,
@@ -3864,7 +3938,7 @@ function renderAnnotation(parent, annotation, index2) {
3864
3938
  }
3865
3939
  if (annotation.line) {
3866
3940
  const line = createSVGElement("line");
3867
- line.setAttribute("class", "viz-annotation-line");
3941
+ line.setAttribute("class", "oc-annotation-line");
3868
3942
  setAttrs(line, {
3869
3943
  x1: annotation.line.start.x,
3870
3944
  y1: annotation.line.start.y,
@@ -3895,7 +3969,7 @@ function renderAnnotation(parent, annotation, index2) {
3895
3969
  const tipY = pointsDown ? midY + caretSize / 2 : midY - caretSize / 2;
3896
3970
  const baseY = pointsDown ? tipY - caretSize : tipY + caretSize;
3897
3971
  const path = createSVGElement("path");
3898
- path.setAttribute("class", "viz-annotation-connector");
3972
+ path.setAttribute("class", "oc-annotation-connector");
3899
3973
  setAttrs(path, {
3900
3974
  d: `M${tipX - caretSize},${baseY} L${tipX},${tipY} L${tipX + caretSize},${baseY}`,
3901
3975
  fill: "none",
@@ -3910,7 +3984,7 @@ function renderAnnotation(parent, annotation, index2) {
3910
3984
  renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
3911
3985
  } else {
3912
3986
  const connector = createSVGElement("line");
3913
- connector.setAttribute("class", "viz-annotation-connector");
3987
+ connector.setAttribute("class", "oc-annotation-connector");
3914
3988
  setAttrs(connector, {
3915
3989
  x1: c2.from.x,
3916
3990
  y1: c2.from.y,
@@ -3924,7 +3998,7 @@ function renderAnnotation(parent, annotation, index2) {
3924
3998
  }
3925
3999
  }
3926
4000
  const text = createSVGElement("text");
3927
- text.setAttribute("class", "viz-annotation-label");
4001
+ text.setAttribute("class", "oc-annotation-label");
3928
4002
  setAttrs(text, { x: annotation.label.x, y: annotation.label.y });
3929
4003
  applyTextStyle(text, annotation.label.style);
3930
4004
  const lines = annotation.label.text.split("\n");
@@ -3949,7 +4023,7 @@ function renderAnnotation(parent, annotation, index2) {
3949
4023
  const pad = 3;
3950
4024
  const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
3951
4025
  const bgRect = createSVGElement("rect");
3952
- bgRect.setAttribute("class", "viz-annotation-bg");
4026
+ bgRect.setAttribute("class", "oc-annotation-bg");
3953
4027
  setAttrs(bgRect, {
3954
4028
  x: bgX,
3955
4029
  y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
@@ -3967,7 +4041,7 @@ function renderAnnotation(parent, annotation, index2) {
3967
4041
  function renderLegend(parent, legend) {
3968
4042
  if (legend.entries.length === 0) return;
3969
4043
  const g = createSVGElement("g");
3970
- g.setAttribute("class", "viz-legend");
4044
+ g.setAttribute("class", "oc-legend");
3971
4045
  g.setAttribute("role", "list");
3972
4046
  g.setAttribute("aria-label", "Chart legend");
3973
4047
  const isHorizontal = legend.position === "top" || legend.position === "bottom";
@@ -3988,7 +4062,7 @@ function renderLegend(parent, legend) {
3988
4062
  }
3989
4063
  }
3990
4064
  const entryG = createSVGElement("g");
3991
- entryG.setAttribute("class", "viz-legend-entry");
4065
+ entryG.setAttribute("class", "oc-legend-entry");
3992
4066
  entryG.setAttribute("role", "listitem");
3993
4067
  entryG.setAttribute("data-legend-index", String(i));
3994
4068
  entryG.setAttribute("data-legend-label", entry.label);
@@ -4088,7 +4162,7 @@ function renderBrand(parent, layout) {
4088
4162
  a2.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
4089
4163
  a2.setAttribute("target", "_blank");
4090
4164
  a2.setAttribute("rel", "noopener");
4091
- a2.setAttribute("class", "viz-chrome-ref");
4165
+ a2.setAttribute("class", "oc-chrome-ref");
4092
4166
  const text = createSVGElement("text");
4093
4167
  setAttrs(text, {
4094
4168
  x: rightEdge,
@@ -4111,8 +4185,10 @@ function renderBrand(parent, layout) {
4111
4185
  a2.appendChild(text);
4112
4186
  parent.appendChild(a2);
4113
4187
  }
4114
- function renderChartSVG(layout, container) {
4188
+ function renderChartSVG(layout, container, opts) {
4115
4189
  const { width, height } = layout.dimensions;
4190
+ const animation = layout.animation;
4191
+ currentAnimation = animation;
4116
4192
  const svg = createSVGElement("svg");
4117
4193
  setAttrs(svg, {
4118
4194
  viewBox: `0 0 ${width} ${height}`,
@@ -4127,7 +4203,30 @@ function renderChartSVG(layout, container) {
4127
4203
  svg.style.height = `${height}px`;
4128
4204
  svg.setAttribute("role", layout.a11y.role);
4129
4205
  svg.setAttribute("aria-label", layout.a11y.altText);
4130
- svg.setAttribute("class", "viz-chart");
4206
+ const classes = opts?.animate ? "oc-chart oc-animate" : "oc-chart";
4207
+ svg.setAttribute("class", classes);
4208
+ if (animation?.enabled) {
4209
+ const markCount = layout.marks.length;
4210
+ const stagger = clampStaggerDelay(animation.staggerDelay, markCount);
4211
+ svg.style.setProperty("--oc-animation-duration", `${animation.duration}ms`);
4212
+ svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
4213
+ svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
4214
+ const easeVar = EASE_VAR_MAP[animation.ease] || EASE_VAR_MAP.smooth;
4215
+ svg.style.setProperty("--oc-animation-ease", easeVar);
4216
+ let maxSegments = 0;
4217
+ for (const m2 of layout.marks) {
4218
+ if (m2.type === "rect") {
4219
+ const pos = m2.stackPos;
4220
+ if (pos !== void 0 && pos + 1 > maxSegments) {
4221
+ maxSegments = pos + 1;
4222
+ }
4223
+ }
4224
+ }
4225
+ if (maxSegments > 0) {
4226
+ const segDuration = Math.round(animation.duration / maxSegments);
4227
+ svg.style.setProperty("--oc-stack-segment-duration", `${segDuration}ms`);
4228
+ }
4229
+ }
4131
4230
  const bg = createSVGElement("rect");
4132
4231
  setAttrs(bg, {
4133
4232
  x: 0,
@@ -4137,7 +4236,7 @@ function renderChartSVG(layout, container) {
4137
4236
  fill: layout.theme.colors.background
4138
4237
  });
4139
4238
  svg.appendChild(bg);
4140
- const clipId = `viz-clip-${Math.random().toString(36).slice(2, 8)}`;
4239
+ const clipId = `oc-clip-${Math.random().toString(36).slice(2, 8)}`;
4141
4240
  const defs = createSVGElement("defs");
4142
4241
  const clipPath = createSVGElement("clipPath");
4143
4242
  clipPath.setAttribute("id", clipId);
@@ -4168,7 +4267,7 @@ function renderChartSVG(layout, container) {
4168
4267
  height: layout.area.height,
4169
4268
  fill: "transparent"
4170
4269
  });
4171
- overlay.setAttribute("class", "viz-voronoi-overlay");
4270
+ overlay.setAttribute("class", "oc-voronoi-overlay");
4172
4271
  overlay.setAttribute("data-voronoi-overlay", "true");
4173
4272
  clippedGroup.appendChild(overlay);
4174
4273
  }
@@ -4177,10 +4276,164 @@ function renderChartSVG(layout, container) {
4177
4276
  renderLegend(svg, layout.legend);
4178
4277
  renderChrome(svg, layout);
4179
4278
  renderBrand(svg, layout);
4279
+ currentAnimation = void 0;
4180
4280
  container.appendChild(svg);
4181
4281
  return svg;
4182
4282
  }
4183
4283
 
4284
+ // src/text-edit-overlay.ts
4285
+ function getScale(svg) {
4286
+ const viewBox = svg.viewBox?.baseVal;
4287
+ const svgRect = svg.getBoundingClientRect();
4288
+ return {
4289
+ scaleX: viewBox?.width && svgRect.width ? svgRect.width / viewBox.width : 1,
4290
+ scaleY: viewBox?.height && svgRect.height ? svgRect.height / viewBox.height : 1
4291
+ };
4292
+ }
4293
+ function getTextStyles(targetElement, scale) {
4294
+ const computed = window.getComputedStyle(targetElement);
4295
+ const svgFontSize = parseFloat(
4296
+ targetElement.getAttribute("font-size") ?? computed.fontSize ?? "14"
4297
+ );
4298
+ const pixelFontSize = svgFontSize * scale.scaleY;
4299
+ const fontFamily = targetElement.getAttribute("font-family") ?? computed.fontFamily ?? "inherit";
4300
+ const fontWeight = targetElement.getAttribute("font-weight") ?? computed.fontWeight ?? "400";
4301
+ const fill = targetElement.style.getPropertyValue("fill") || targetElement.getAttribute("fill") || computed.color || "#000";
4302
+ const textAnchor = targetElement.getAttribute("text-anchor") ?? "start";
4303
+ let textAlign = "left";
4304
+ if (textAnchor === "middle") textAlign = "center";
4305
+ else if (textAnchor === "end") textAlign = "right";
4306
+ return {
4307
+ fontFamily,
4308
+ fontSize: `${pixelFontSize}px`,
4309
+ fontWeight,
4310
+ color: fill,
4311
+ textAlign,
4312
+ lineHeight: "1.3"
4313
+ };
4314
+ }
4315
+ function computePosition2(targetElement, svg, container) {
4316
+ const bbox = targetElement.getBBox();
4317
+ const scale = getScale(svg);
4318
+ const svgRect = svg.getBoundingClientRect();
4319
+ const containerRect = container.getBoundingClientRect();
4320
+ const padding = 4;
4321
+ const left = bbox.x * scale.scaleX + (svgRect.left - containerRect.left) - padding;
4322
+ const top = bbox.y * scale.scaleY + (svgRect.top - containerRect.top) - padding;
4323
+ const width = bbox.width * scale.scaleX + padding * 2;
4324
+ const height = bbox.height * scale.scaleY + padding * 2;
4325
+ return {
4326
+ top: Math.max(0, top),
4327
+ left: Math.max(0, left),
4328
+ width: Math.max(width, 60),
4329
+ height: Math.max(height, 24)
4330
+ };
4331
+ }
4332
+ function createTextEditOverlay(config) {
4333
+ const { container, svg, targetElement, currentText, onCommit, onCancel } = config;
4334
+ let destroyed = false;
4335
+ const originalOpacity = targetElement.getAttribute("opacity");
4336
+ targetElement.setAttribute("opacity", "0");
4337
+ const scale = getScale(svg);
4338
+ const styles = getTextStyles(targetElement, scale);
4339
+ const position = computePosition2(targetElement, svg, container);
4340
+ const textarea = document.createElement("textarea");
4341
+ textarea.value = currentText;
4342
+ const containerPosition = window.getComputedStyle(container).position;
4343
+ const containerWasStatic = containerPosition === "static";
4344
+ if (containerWasStatic) {
4345
+ container.style.position = "relative";
4346
+ }
4347
+ Object.assign(textarea.style, {
4348
+ position: "absolute",
4349
+ top: `${position.top}px`,
4350
+ left: `${position.left}px`,
4351
+ width: `${position.width}px`,
4352
+ minHeight: `${position.height}px`,
4353
+ fontFamily: styles.fontFamily,
4354
+ fontSize: styles.fontSize,
4355
+ fontWeight: styles.fontWeight,
4356
+ color: styles.color,
4357
+ textAlign: styles.textAlign,
4358
+ lineHeight: styles.lineHeight,
4359
+ padding: "2px 4px",
4360
+ margin: "0",
4361
+ border: "1px solid rgba(79, 70, 229, 0.4)",
4362
+ borderRadius: "3px",
4363
+ background: "rgba(255, 255, 255, 0.95)",
4364
+ outline: "none",
4365
+ resize: "none",
4366
+ overflow: "hidden",
4367
+ boxSizing: "border-box",
4368
+ zIndex: "10000",
4369
+ // Remove default textarea appearance
4370
+ WebkitAppearance: "none",
4371
+ appearance: "none"
4372
+ });
4373
+ container.appendChild(textarea);
4374
+ textarea.focus();
4375
+ textarea.select();
4376
+ function destroy() {
4377
+ if (destroyed) return;
4378
+ destroyed = true;
4379
+ if (originalOpacity !== null) {
4380
+ targetElement.setAttribute("opacity", originalOpacity);
4381
+ } else {
4382
+ targetElement.removeAttribute("opacity");
4383
+ }
4384
+ textarea.removeEventListener("keydown", handleKeyDown);
4385
+ document.removeEventListener("mousedown", handleClickOutside);
4386
+ resizeObserver.disconnect();
4387
+ if (containerWasStatic) {
4388
+ container.style.position = "";
4389
+ }
4390
+ if (textarea.parentNode) {
4391
+ textarea.parentNode.removeChild(textarea);
4392
+ }
4393
+ }
4394
+ function commit() {
4395
+ if (destroyed) return;
4396
+ const newText = textarea.value;
4397
+ destroy();
4398
+ onCommit(newText);
4399
+ }
4400
+ function cancel() {
4401
+ if (destroyed) return;
4402
+ destroy();
4403
+ onCancel();
4404
+ }
4405
+ const handleKeyDown = (e) => {
4406
+ if (e.key === "Enter" && !e.shiftKey) {
4407
+ e.preventDefault();
4408
+ commit();
4409
+ } else if (e.key === "Escape") {
4410
+ e.preventDefault();
4411
+ cancel();
4412
+ }
4413
+ };
4414
+ textarea.addEventListener("keydown", handleKeyDown);
4415
+ const handleClickOutside = (e) => {
4416
+ if (!textarea.contains(e.target)) {
4417
+ commit();
4418
+ }
4419
+ };
4420
+ requestAnimationFrame(() => {
4421
+ if (!destroyed) {
4422
+ document.addEventListener("mousedown", handleClickOutside);
4423
+ }
4424
+ });
4425
+ const resizeObserver = new ResizeObserver(() => {
4426
+ if (destroyed) return;
4427
+ const newPosition = computePosition2(targetElement, svg, container);
4428
+ textarea.style.top = `${newPosition.top}px`;
4429
+ textarea.style.left = `${newPosition.left}px`;
4430
+ textarea.style.width = `${newPosition.width}px`;
4431
+ textarea.style.minHeight = `${newPosition.height}px`;
4432
+ });
4433
+ resizeObserver.observe(container);
4434
+ return { destroy };
4435
+ }
4436
+
4184
4437
  // src/mount.ts
4185
4438
  function resolveDarkMode2(mode) {
4186
4439
  if (mode === "force") return true;
@@ -4431,7 +4684,7 @@ function wireChartEvents(svg, layout, specAnnotations, handlers) {
4431
4684
  }
4432
4685
  }
4433
4686
  if (handlers.onAnnotationClick) {
4434
- const annotationElements = svg.querySelectorAll(".viz-annotation");
4687
+ const annotationElements = svg.querySelectorAll(".oc-annotation");
4435
4688
  for (let i = 0; i < annotationElements.length; i++) {
4436
4689
  const el = annotationElements[i];
4437
4690
  const specAnnotation = specAnnotations[i];
@@ -4458,7 +4711,7 @@ function createDragHandler(config) {
4458
4711
  let activeDocTouchMove = null;
4459
4712
  let activeDocTouchEnd = null;
4460
4713
  let activeDocTouchCancel = null;
4461
- function getScale() {
4714
+ function getScale2() {
4462
4715
  const viewBox = svg.viewBox?.baseVal;
4463
4716
  const svgRect = svg.getBoundingClientRect();
4464
4717
  return {
@@ -4468,7 +4721,7 @@ function createDragHandler(config) {
4468
4721
  }
4469
4722
  function startDrag(startX, startY) {
4470
4723
  setDragging(true);
4471
- const { scaleX, scaleY } = getScale();
4724
+ const { scaleX, scaleY } = getScale2();
4472
4725
  element.style.cursor = "grabbing";
4473
4726
  svg.style.userSelect = "none";
4474
4727
  const handleMove = (clientX, clientY) => {
@@ -4594,7 +4847,7 @@ function createDragHandler(config) {
4594
4847
  };
4595
4848
  }
4596
4849
  function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setDragging) {
4597
- const annotationElements = svg.querySelectorAll(".viz-annotation-text");
4850
+ const annotationElements = svg.querySelectorAll(".oc-annotation-text");
4598
4851
  const cleanups = [];
4599
4852
  for (const el of annotationElements) {
4600
4853
  const indexStr = el.getAttribute("data-annotation-index");
@@ -4605,11 +4858,11 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
4605
4858
  const textAnnotation = specAnnotation;
4606
4859
  const annotationG = el;
4607
4860
  annotationG.style.cursor = "grab";
4608
- const connectorLine = annotationG.querySelector("line.viz-annotation-connector");
4861
+ const connectorLine = annotationG.querySelector("line.oc-annotation-connector");
4609
4862
  const origX2 = connectorLine ? Number(connectorLine.getAttribute("x2")) : 0;
4610
4863
  const origY2 = connectorLine ? Number(connectorLine.getAttribute("y2")) : 0;
4611
- const curvedPath = annotationG.querySelector("path.viz-annotation-connector");
4612
- const arrowhead = annotationG.querySelector("polygon.viz-annotation-connector");
4864
+ const curvedPath = annotationG.querySelector("path.oc-annotation-connector");
4865
+ const arrowhead = annotationG.querySelector("polygon.oc-annotation-connector");
4613
4866
  const hasCurvedConnector = curvedPath !== null;
4614
4867
  const origDx = textAnnotation.offset?.dx ?? 0;
4615
4868
  const origDy = textAnnotation.offset?.dy ?? 0;
@@ -4659,7 +4912,7 @@ function wireAnnotationDrag(svg, specAnnotations, onAnnotationEdit, onEdit, setD
4659
4912
  function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4660
4913
  const SVG_NS2 = "http://www.w3.org/2000/svg";
4661
4914
  const cleanups = [];
4662
- const annotationGroups = svg.querySelectorAll(".viz-annotation-text");
4915
+ const annotationGroups = svg.querySelectorAll(".oc-annotation-text");
4663
4916
  for (const el of annotationGroups) {
4664
4917
  const annotationG = el;
4665
4918
  const indexStr = annotationG.getAttribute("data-annotation-index");
@@ -4668,8 +4921,8 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4668
4921
  const specAnnotation = specAnnotations[index2];
4669
4922
  if (!specAnnotation || specAnnotation.type !== "text") continue;
4670
4923
  const textAnnotation = specAnnotation;
4671
- const connectorLine = annotationG.querySelector("line.viz-annotation-connector");
4672
- const curvedPath = annotationG.querySelector("path.viz-annotation-connector");
4924
+ const connectorLine = annotationG.querySelector("line.oc-annotation-connector");
4925
+ const curvedPath = annotationG.querySelector("path.oc-annotation-connector");
4673
4926
  if (!connectorLine && !curvedPath) continue;
4674
4927
  let fromX, fromY, toX, toY;
4675
4928
  if (connectorLine) {
@@ -4682,7 +4935,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4682
4935
  const mMatch = pathD.match(/M\s*([\d.e+-]+)\s+([\d.e+-]+)/);
4683
4936
  fromX = mMatch ? Number(mMatch[1]) : 0;
4684
4937
  fromY = mMatch ? Number(mMatch[2]) : 0;
4685
- const arrowhead = annotationG.querySelector("polygon.viz-annotation-connector");
4938
+ const arrowhead = annotationG.querySelector("polygon.oc-annotation-connector");
4686
4939
  const points = arrowhead?.getAttribute("points") ?? "";
4687
4940
  const firstPoint = points.split(" ")[0] ?? "0,0";
4688
4941
  const [px, py] = firstPoint.split(",");
@@ -4697,7 +4950,7 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4697
4950
  for (const ep of endpoints) {
4698
4951
  if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
4699
4952
  const handleEl = document.createElementNS(SVG_NS2, "circle");
4700
- handleEl.setAttribute("class", "viz-connector-handle");
4953
+ handleEl.setAttribute("class", "oc-connector-handle");
4701
4954
  handleEl.setAttribute("data-endpoint", ep.name);
4702
4955
  handleEl.setAttribute("cx", String(ep.cx));
4703
4956
  handleEl.setAttribute("cy", String(ep.cy));
@@ -4791,13 +5044,13 @@ function wireConnectorEndpointDrag(svg, specAnnotations, onEdit, setDragging) {
4791
5044
  function wireAnnotationLabelDrag(svg, specAnnotations, onEdit, setDragging) {
4792
5045
  const cleanups = [];
4793
5046
  const selectors = [
4794
- ".viz-annotation-range .viz-annotation-label",
4795
- ".viz-annotation-refline .viz-annotation-label"
5047
+ ".oc-annotation-range .oc-annotation-label",
5048
+ ".oc-annotation-refline .oc-annotation-label"
4796
5049
  ];
4797
5050
  for (const selector of selectors) {
4798
5051
  const labels = svg.querySelectorAll(selector);
4799
5052
  for (const label of labels) {
4800
- const annotationG = label.closest(".viz-annotation");
5053
+ const annotationG = label.closest(".oc-annotation");
4801
5054
  if (!annotationG) continue;
4802
5055
  const indexStr = annotationG.getAttribute("data-annotation-index");
4803
5056
  if (indexStr === null) continue;
@@ -4846,7 +5099,7 @@ function wireAnnotationLabelDrag(svg, specAnnotations, onEdit, setDragging) {
4846
5099
  };
4847
5100
  }
4848
5101
  function wireChromeDrag(svg, spec, onEdit, setDragging) {
4849
- const chromeTexts = svg.querySelectorAll(".viz-chrome text[data-chrome-key]");
5102
+ const chromeTexts = svg.querySelectorAll(".oc-chrome text[data-chrome-key]");
4850
5103
  const cleanups = [];
4851
5104
  const chromeConfig = "chrome" in spec ? spec.chrome : void 0;
4852
5105
  for (const el of chromeTexts) {
@@ -4886,7 +5139,7 @@ function wireChromeDrag(svg, spec, onEdit, setDragging) {
4886
5139
  };
4887
5140
  }
4888
5141
  function wireLegendDrag(svg, spec, onEdit, setDragging) {
4889
- const legendG = svg.querySelector(".viz-legend");
5142
+ const legendG = svg.querySelector(".oc-legend");
4890
5143
  if (!legendG) return () => {
4891
5144
  };
4892
5145
  const cleanups = [];
@@ -4916,7 +5169,7 @@ function wireLegendDrag(svg, spec, onEdit, setDragging) {
4916
5169
  };
4917
5170
  }
4918
5171
  function wireSeriesLabelDrag(svg, spec, onEdit, setDragging) {
4919
- const labels = svg.querySelectorAll(".viz-mark-label");
5172
+ const labels = svg.querySelectorAll(".oc-mark-label");
4920
5173
  const cleanups = [];
4921
5174
  const labelsConfig = "labels" in spec ? spec.labels : void 0;
4922
5175
  for (const label of labels) {
@@ -4975,7 +5228,7 @@ function wireLegendInteraction(svg, _layout, onLegendToggle, onEdit) {
4975
5228
  onLegendToggle?.(label, false);
4976
5229
  onEdit?.({ type: "legend-toggle", series: label, hidden: true });
4977
5230
  }
4978
- const marks = svg.querySelectorAll(".viz-mark");
5231
+ const marks = svg.querySelectorAll(".oc-mark");
4979
5232
  for (const mark of marks) {
4980
5233
  const seriesName = mark.getAttribute("data-series");
4981
5234
  if (!seriesName) continue;
@@ -5010,13 +5263,13 @@ function wireKeyboardNav(svg, container, tooltipDescriptors, tooltipManager, lay
5010
5263
  let focusIndex = -1;
5011
5264
  function highlightMark(index2) {
5012
5265
  if (focusIndex >= 0 && focusIndex < markElements.length) {
5013
- markElements[focusIndex].classList.remove("viz-mark-focused");
5266
+ markElements[focusIndex].classList.remove("oc-mark-focused");
5014
5267
  markElements[focusIndex].removeAttribute("aria-selected");
5015
5268
  }
5016
5269
  focusIndex = index2;
5017
5270
  if (focusIndex >= 0 && focusIndex < markElements.length) {
5018
5271
  const el = markElements[focusIndex];
5019
- el.classList.add("viz-mark-focused");
5272
+ el.classList.add("oc-mark-focused");
5020
5273
  el.setAttribute("aria-selected", "true");
5021
5274
  }
5022
5275
  }
@@ -5083,7 +5336,7 @@ function createScreenReaderTable(layout, container) {
5083
5336
  const data = layout.a11y.dataTableFallback;
5084
5337
  if (!data || data.length === 0) return null;
5085
5338
  const table = document.createElement("table");
5086
- table.className = "viz-sr-only";
5339
+ table.className = "oc-sr-only";
5087
5340
  table.style.position = "absolute";
5088
5341
  table.style.width = "1px";
5089
5342
  table.style.height = "1px";
@@ -5125,6 +5378,167 @@ function createScreenReaderTable(layout, container) {
5125
5378
  container.appendChild(table);
5126
5379
  return table;
5127
5380
  }
5381
+ var EDITABLE_HOVER_CSS = `
5382
+ .oc-editable-hover {
5383
+ outline: 1.5px solid rgba(79, 70, 229, 0.35);
5384
+ outline-offset: 2px;
5385
+ border-radius: 2px;
5386
+ }
5387
+ `;
5388
+ function makeEditable(svg) {
5389
+ svg.setAttribute("tabindex", "0");
5390
+ svg.style.outline = "none";
5391
+ const style = document.createElementNS("http://www.w3.org/2000/svg", "style");
5392
+ style.textContent = EDITABLE_HOVER_CSS;
5393
+ svg.insertBefore(style, svg.firstChild);
5394
+ }
5395
+ function hasEditingCallbacks(opts) {
5396
+ return !!(opts?.onEdit || opts?.onSelect || opts?.onDeselect || opts?.onTextEdit);
5397
+ }
5398
+ function findElementByRef(svg, ref) {
5399
+ switch (ref.type) {
5400
+ case "annotation": {
5401
+ if (ref.id) {
5402
+ const byId = svg.querySelector(`[data-annotation-id="${ref.id}"]`);
5403
+ if (byId) return byId;
5404
+ }
5405
+ return svg.querySelector(`[data-annotation-index="${ref.index}"]`);
5406
+ }
5407
+ case "chrome":
5408
+ return svg.querySelector(`[data-chrome-key="${ref.key}"]`);
5409
+ case "series-label":
5410
+ return svg.querySelector(`.oc-mark-label[data-series="${ref.series}"]`);
5411
+ case "legend":
5412
+ return svg.querySelector(".oc-legend");
5413
+ case "legend-entry":
5414
+ return svg.querySelector(`[data-legend-index="${ref.index}"]`);
5415
+ }
5416
+ }
5417
+ function buildElementRef(element, _specAnnotations) {
5418
+ const annotationEl = element.closest("[data-annotation-index]");
5419
+ if (annotationEl) {
5420
+ const index2 = Number(annotationEl.getAttribute("data-annotation-index"));
5421
+ const id = annotationEl.getAttribute("data-annotation-id") ?? void 0;
5422
+ return elementRef.annotation(index2, id);
5423
+ }
5424
+ const chromeEl = element.closest("[data-chrome-key]");
5425
+ if (chromeEl) {
5426
+ const key = chromeEl.getAttribute("data-chrome-key");
5427
+ if (key) return elementRef.chrome(key);
5428
+ }
5429
+ const seriesLabelEl = element.closest(".oc-mark-label[data-series]");
5430
+ if (seriesLabelEl) {
5431
+ const series = seriesLabelEl.getAttribute("data-series");
5432
+ if (series) return elementRef.seriesLabel(series);
5433
+ }
5434
+ const legendEntryEl = element.closest("[data-legend-index]");
5435
+ if (legendEntryEl) {
5436
+ const index2 = Number(legendEntryEl.getAttribute("data-legend-index"));
5437
+ const series = legendEntryEl.getAttribute("data-legend-label") ?? "";
5438
+ return elementRef.legendEntry(series, index2);
5439
+ }
5440
+ const legendEl = element.closest(".oc-legend");
5441
+ if (legendEl) return elementRef.legend();
5442
+ return null;
5443
+ }
5444
+ function getEditableElements(spec, layout) {
5445
+ const refs = [];
5446
+ const chromeKeys = ["title", "subtitle", "source", "byline", "footer"];
5447
+ for (const key of chromeKeys) {
5448
+ if (layout.chrome[key]) {
5449
+ refs.push(elementRef.chrome(key));
5450
+ }
5451
+ }
5452
+ const annotations = "annotations" in spec && Array.isArray(spec.annotations) ? spec.annotations : [];
5453
+ for (let i = 0; i < annotations.length; i++) {
5454
+ refs.push(elementRef.annotation(i, annotations[i].id));
5455
+ }
5456
+ const seriesLabels = [];
5457
+ for (const mark of layout.marks) {
5458
+ if (mark.type === "line" && mark.label?.visible && mark.seriesKey) {
5459
+ seriesLabels.push(mark.seriesKey);
5460
+ }
5461
+ }
5462
+ seriesLabels.sort();
5463
+ for (const series of seriesLabels) {
5464
+ refs.push(elementRef.seriesLabel(series));
5465
+ }
5466
+ if (layout.legend.entries.length > 0) {
5467
+ refs.push(elementRef.legend());
5468
+ }
5469
+ return refs;
5470
+ }
5471
+ function isTextEditable(ref, specAnnotations) {
5472
+ if (ref.type === "chrome") return true;
5473
+ if (ref.type === "annotation") {
5474
+ const annotation = specAnnotations[ref.index];
5475
+ return annotation?.type === "text";
5476
+ }
5477
+ return false;
5478
+ }
5479
+ function getElementText(ref, spec) {
5480
+ if (ref.type === "chrome") {
5481
+ const chromeConfig = "chrome" in spec ? spec.chrome : void 0;
5482
+ if (!chromeConfig) return null;
5483
+ const entry = chromeConfig[ref.key];
5484
+ if (typeof entry === "string") return entry;
5485
+ if (typeof entry === "object" && entry !== null && "text" in entry) {
5486
+ return entry.text;
5487
+ }
5488
+ return null;
5489
+ }
5490
+ if (ref.type === "annotation") {
5491
+ const annotations = "annotations" in spec && Array.isArray(spec.annotations) ? spec.annotations : [];
5492
+ const annotation = annotations[ref.index];
5493
+ if (annotation?.type === "text") return annotation.text ?? null;
5494
+ if (annotation?.label) return annotation.label;
5495
+ return null;
5496
+ }
5497
+ return null;
5498
+ }
5499
+ function refsEqual(a2, b) {
5500
+ if (a2 === null || b === null) return a2 === b;
5501
+ if (a2.type !== b.type) return false;
5502
+ switch (a2.type) {
5503
+ case "annotation": {
5504
+ const bAnno = b;
5505
+ if (a2.id && bAnno.id) return a2.id === bAnno.id;
5506
+ return a2.index === bAnno.index;
5507
+ }
5508
+ case "chrome":
5509
+ return a2.key === b.key;
5510
+ case "series-label":
5511
+ return a2.series === b.series;
5512
+ case "legend":
5513
+ return true;
5514
+ case "legend-entry": {
5515
+ const bEntry = b;
5516
+ return a2.index === bEntry.index && a2.series === bEntry.series;
5517
+ }
5518
+ }
5519
+ }
5520
+ function renderSelectionOverlay(svg, ref, layout) {
5521
+ const target = findElementByRef(svg, ref);
5522
+ if (!target) return null;
5523
+ const bbox = target.getBBox();
5524
+ const padding = 4;
5525
+ const accentColor = layout.theme.colors.categorical?.[0] ?? "#4f46e5";
5526
+ const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
5527
+ g.setAttribute("class", "oc-selection-overlay");
5528
+ const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
5529
+ rect.setAttribute("x", String(bbox.x - padding));
5530
+ rect.setAttribute("y", String(bbox.y - padding));
5531
+ rect.setAttribute("width", String(bbox.width + padding * 2));
5532
+ rect.setAttribute("height", String(bbox.height + padding * 2));
5533
+ rect.setAttribute("rx", "3");
5534
+ rect.setAttribute("fill", "transparent");
5535
+ rect.setAttribute("stroke", accentColor);
5536
+ rect.setAttribute("stroke-width", "1.5");
5537
+ rect.setAttribute("pointer-events", "none");
5538
+ g.appendChild(rect);
5539
+ svg.appendChild(g);
5540
+ return g;
5541
+ }
5128
5542
  function createChart(container, spec, options) {
5129
5543
  let currentSpec = spec;
5130
5544
  let currentLayout;
@@ -5138,11 +5552,19 @@ function createChart(container, spec, options) {
5138
5552
  let cleanupChartEvents = null;
5139
5553
  let cleanupAnnotationDrag = null;
5140
5554
  let cleanupEditDrags = null;
5555
+ let cleanupSelection = null;
5556
+ let cleanupKeyboardEdit = null;
5141
5557
  let srTable = null;
5142
5558
  let destroyed = false;
5143
5559
  let isDragging = false;
5144
5560
  let pendingRender = false;
5145
5561
  let resizeTimer = null;
5562
+ let isFirstRender = true;
5563
+ let cleanupAnimations = null;
5564
+ let selectedElement = options?.selectedElement ?? null;
5565
+ let overlayElement = null;
5566
+ let isTextEditingActive = false;
5567
+ let textEditCleanup = null;
5146
5568
  const measureText = createMeasureText();
5147
5569
  function compile() {
5148
5570
  const { width, height } = getContainerDimensions();
@@ -5166,11 +5588,208 @@ function createChart(container, spec, options) {
5166
5588
  height: Math.max(rect.height || 400, 100)
5167
5589
  };
5168
5590
  }
5591
+ function getSpecAnnotations() {
5592
+ return "annotations" in currentSpec && Array.isArray(currentSpec.annotations) ? currentSpec.annotations : [];
5593
+ }
5594
+ function selectElement(ref) {
5595
+ if (!svgElement) return;
5596
+ const target = findElementByRef(svgElement, ref);
5597
+ if (!target) return;
5598
+ if (selectedElement && !refsEqual(selectedElement, ref)) {
5599
+ deselectElement();
5600
+ }
5601
+ selectedElement = ref;
5602
+ overlayElement = renderSelectionOverlay(svgElement, ref, currentLayout);
5603
+ options?.onSelect?.(ref);
5604
+ svgElement.focus();
5605
+ }
5606
+ function deselectElement() {
5607
+ if (!selectedElement) return;
5608
+ if (isTextEditingActive && textEditCleanup) {
5609
+ textEditCleanup();
5610
+ textEditCleanup = null;
5611
+ isTextEditingActive = false;
5612
+ }
5613
+ const prev = selectedElement;
5614
+ selectedElement = null;
5615
+ if (overlayElement?.parentNode) {
5616
+ overlayElement.parentNode.removeChild(overlayElement);
5617
+ }
5618
+ overlayElement = null;
5619
+ options?.onDeselect?.(prev);
5620
+ }
5621
+ function enterTextEditing() {
5622
+ if (!svgElement || !selectedElement || isTextEditingActive) return;
5623
+ const specAnnotations = getSpecAnnotations();
5624
+ if (!isTextEditable(selectedElement, specAnnotations)) return;
5625
+ const currentText = getElementText(selectedElement, currentSpec);
5626
+ if (currentText === null) return;
5627
+ const target = findElementByRef(svgElement, selectedElement);
5628
+ if (!target) return;
5629
+ const textEl = target.tagName === "text" ? target : target.querySelector("text");
5630
+ if (!textEl) return;
5631
+ isTextEditingActive = true;
5632
+ const editRef = selectedElement;
5633
+ const overlay = createTextEditOverlay({
5634
+ container,
5635
+ svg: svgElement,
5636
+ targetElement: textEl,
5637
+ currentText,
5638
+ onCommit: (newText) => {
5639
+ isTextEditingActive = false;
5640
+ textEditCleanup = null;
5641
+ if (newText !== currentText) {
5642
+ options?.onTextEdit?.(editRef, currentText, newText);
5643
+ options?.onEdit?.({
5644
+ type: "text-edit",
5645
+ element: editRef,
5646
+ oldText: currentText,
5647
+ newText
5648
+ });
5649
+ }
5650
+ },
5651
+ onCancel: () => {
5652
+ isTextEditingActive = false;
5653
+ textEditCleanup = null;
5654
+ }
5655
+ });
5656
+ textEditCleanup = overlay.destroy;
5657
+ }
5658
+ function wireSelectionEvents() {
5659
+ if (!svgElement) return () => {
5660
+ };
5661
+ const svg = svgElement;
5662
+ const cleanups = [];
5663
+ const handleClick = (e) => {
5664
+ const mouseEvent = e;
5665
+ const target = mouseEvent.target;
5666
+ if (isTextEditingActive) return;
5667
+ const specAnnotations = getSpecAnnotations();
5668
+ const ref = buildElementRef(target, specAnnotations);
5669
+ if (ref) {
5670
+ selectElement(ref);
5671
+ } else {
5672
+ deselectElement();
5673
+ }
5674
+ };
5675
+ svg.addEventListener("click", handleClick);
5676
+ cleanups.push(() => svg.removeEventListener("click", handleClick));
5677
+ const handleMouseEnter = (e) => {
5678
+ const target = e.target.closest(
5679
+ "[data-annotation-index], [data-chrome-key], .oc-mark-label[data-series], .oc-legend, [data-legend-index]"
5680
+ );
5681
+ if (target) {
5682
+ target.classList.add("oc-editable-hover");
5683
+ }
5684
+ };
5685
+ const handleMouseLeave = (e) => {
5686
+ const target = e.target.closest(".oc-editable-hover");
5687
+ if (target) {
5688
+ target.classList.remove("oc-editable-hover");
5689
+ }
5690
+ };
5691
+ svg.addEventListener("mouseenter", handleMouseEnter, true);
5692
+ svg.addEventListener("mouseleave", handleMouseLeave, true);
5693
+ cleanups.push(() => {
5694
+ svg.removeEventListener("mouseenter", handleMouseEnter, true);
5695
+ svg.removeEventListener("mouseleave", handleMouseLeave, true);
5696
+ });
5697
+ const handleDblClick = (e) => {
5698
+ const mouseEvent = e;
5699
+ const target = mouseEvent.target;
5700
+ const specAnnotations = getSpecAnnotations();
5701
+ const ref = buildElementRef(target, specAnnotations);
5702
+ if (ref && isTextEditable(ref, specAnnotations)) {
5703
+ if (!refsEqual(selectedElement, ref)) {
5704
+ selectElement(ref);
5705
+ }
5706
+ enterTextEditing();
5707
+ }
5708
+ };
5709
+ svg.addEventListener("dblclick", handleDblClick);
5710
+ cleanups.push(() => svg.removeEventListener("dblclick", handleDblClick));
5711
+ return () => {
5712
+ for (const cleanup of cleanups) {
5713
+ cleanup();
5714
+ }
5715
+ };
5716
+ }
5717
+ function wireKeyboardEditEvents() {
5718
+ if (!svgElement) return () => {
5719
+ };
5720
+ const svg = svgElement;
5721
+ const handleKeyDown = (e) => {
5722
+ const specAnnotations = getSpecAnnotations();
5723
+ switch (e.key) {
5724
+ case "Delete":
5725
+ case "Backspace": {
5726
+ if (selectedElement && !isTextEditingActive) {
5727
+ e.preventDefault();
5728
+ options?.onEdit?.({ type: "delete", element: selectedElement });
5729
+ }
5730
+ break;
5731
+ }
5732
+ case "Escape": {
5733
+ e.preventDefault();
5734
+ if (isTextEditingActive && textEditCleanup) {
5735
+ textEditCleanup();
5736
+ textEditCleanup = null;
5737
+ isTextEditingActive = false;
5738
+ } else if (selectedElement) {
5739
+ deselectElement();
5740
+ }
5741
+ break;
5742
+ }
5743
+ case "ArrowDown":
5744
+ case "ArrowRight": {
5745
+ if (!isTextEditingActive && selectedElement) {
5746
+ e.preventDefault();
5747
+ const editables = getEditableElements(currentSpec, currentLayout);
5748
+ if (editables.length === 0) break;
5749
+ const currentIndex = editables.findIndex((r) => refsEqual(r, selectedElement));
5750
+ const nextIndex = currentIndex >= editables.length - 1 ? 0 : currentIndex + 1;
5751
+ selectElement(editables[nextIndex]);
5752
+ }
5753
+ break;
5754
+ }
5755
+ case "ArrowUp":
5756
+ case "ArrowLeft": {
5757
+ if (!isTextEditingActive && selectedElement) {
5758
+ e.preventDefault();
5759
+ const editables = getEditableElements(currentSpec, currentLayout);
5760
+ if (editables.length === 0) break;
5761
+ const currentIndex = editables.findIndex((r) => refsEqual(r, selectedElement));
5762
+ const nextIndex = currentIndex <= 0 ? editables.length - 1 : currentIndex - 1;
5763
+ selectElement(editables[nextIndex]);
5764
+ }
5765
+ break;
5766
+ }
5767
+ case "Enter": {
5768
+ if (selectedElement && !isTextEditingActive) {
5769
+ if (isTextEditable(selectedElement, specAnnotations)) {
5770
+ e.preventDefault();
5771
+ enterTextEditing();
5772
+ }
5773
+ }
5774
+ break;
5775
+ }
5776
+ }
5777
+ };
5778
+ svg.addEventListener("keydown", handleKeyDown);
5779
+ return () => {
5780
+ svg.removeEventListener("keydown", handleKeyDown);
5781
+ };
5782
+ }
5169
5783
  function render() {
5170
5784
  if (isDragging) {
5171
5785
  pendingRender = true;
5172
5786
  return;
5173
5787
  }
5788
+ if (cleanupAnimations) {
5789
+ cleanupAnimations();
5790
+ cleanupAnimations = null;
5791
+ }
5792
+ cancelAnimations(svgElement);
5174
5793
  if (cleanupTooltipEvents) {
5175
5794
  cleanupTooltipEvents();
5176
5795
  cleanupTooltipEvents = null;
@@ -5199,6 +5818,20 @@ function createChart(container, spec, options) {
5199
5818
  cleanupEditDrags();
5200
5819
  cleanupEditDrags = null;
5201
5820
  }
5821
+ if (cleanupSelection) {
5822
+ cleanupSelection();
5823
+ cleanupSelection = null;
5824
+ }
5825
+ if (cleanupKeyboardEdit) {
5826
+ cleanupKeyboardEdit();
5827
+ cleanupKeyboardEdit = null;
5828
+ }
5829
+ if (textEditCleanup) {
5830
+ textEditCleanup();
5831
+ textEditCleanup = null;
5832
+ isTextEditingActive = false;
5833
+ }
5834
+ overlayElement = null;
5202
5835
  if (svgElement?.parentNode) {
5203
5836
  svgElement.parentNode.removeChild(svgElement);
5204
5837
  }
@@ -5210,7 +5843,8 @@ function createChart(container, spec, options) {
5210
5843
  srTable = null;
5211
5844
  }
5212
5845
  currentLayout = compile();
5213
- svgElement = renderChartSVG(currentLayout, container);
5846
+ const shouldAnimate = isFirstRender && !!currentLayout.animation?.enabled;
5847
+ svgElement = renderChartSVG(currentLayout, container, { animate: shouldAnimate });
5214
5848
  tooltipManager = createTooltipManager(container);
5215
5849
  cleanupTooltipEvents = wireTooltipEvents(
5216
5850
  svgElement,
@@ -5270,22 +5904,46 @@ function createChart(container, spec, options) {
5270
5904
  }
5271
5905
  };
5272
5906
  }
5907
+ if (hasEditingCallbacks(options)) {
5908
+ makeEditable(svgElement);
5909
+ cleanupSelection = wireSelectionEvents();
5910
+ cleanupKeyboardEdit = wireKeyboardEditEvents();
5911
+ if (selectedElement) {
5912
+ const target = findElementByRef(svgElement, selectedElement);
5913
+ if (target) {
5914
+ overlayElement = renderSelectionOverlay(svgElement, selectedElement, currentLayout);
5915
+ } else {
5916
+ selectedElement = null;
5917
+ overlayElement = null;
5918
+ }
5919
+ }
5920
+ }
5273
5921
  srTable = createScreenReaderTable(currentLayout, container);
5274
- container.classList.add("viz-root");
5922
+ container.classList.add("oc-root");
5275
5923
  const isDark = resolveDarkMode2(options?.darkMode);
5276
5924
  if (isDark) {
5277
- container.classList.add("viz-dark");
5925
+ container.classList.add("oc-dark");
5278
5926
  } else {
5279
- container.classList.remove("viz-dark");
5927
+ container.classList.remove("oc-dark");
5928
+ }
5929
+ if (shouldAnimate && svgElement) {
5930
+ cleanupAnimations = setupAnimationCleanup(svgElement);
5931
+ }
5932
+ if (isFirstRender) {
5933
+ isFirstRender = false;
5280
5934
  }
5281
5935
  }
5282
- function update(newSpec) {
5936
+ function update(newSpec, updateOpts) {
5283
5937
  if (destroyed) return;
5284
5938
  currentSpec = newSpec;
5939
+ if (updateOpts && "selectedElement" in updateOpts) {
5940
+ selectedElement = updateOpts.selectedElement ?? null;
5941
+ }
5285
5942
  render();
5286
5943
  }
5287
5944
  function resize() {
5288
5945
  if (destroyed) return;
5946
+ if (cleanupAnimations) return;
5289
5947
  render();
5290
5948
  }
5291
5949
  function doExport(format, exportOptions) {
@@ -5312,6 +5970,11 @@ function createChart(container, spec, options) {
5312
5970
  function destroy() {
5313
5971
  if (destroyed) return;
5314
5972
  destroyed = true;
5973
+ if (cleanupAnimations) {
5974
+ cleanupAnimations();
5975
+ cleanupAnimations = null;
5976
+ }
5977
+ cancelAnimations(svgElement);
5315
5978
  if (resizeTimer !== null) {
5316
5979
  clearTimeout(resizeTimer);
5317
5980
  resizeTimer = null;
@@ -5344,6 +6007,21 @@ function createChart(container, spec, options) {
5344
6007
  cleanupEditDrags();
5345
6008
  cleanupEditDrags = null;
5346
6009
  }
6010
+ if (cleanupSelection) {
6011
+ cleanupSelection();
6012
+ cleanupSelection = null;
6013
+ }
6014
+ if (cleanupKeyboardEdit) {
6015
+ cleanupKeyboardEdit();
6016
+ cleanupKeyboardEdit = null;
6017
+ }
6018
+ if (textEditCleanup) {
6019
+ textEditCleanup();
6020
+ textEditCleanup = null;
6021
+ isTextEditingActive = false;
6022
+ }
6023
+ selectedElement = null;
6024
+ overlayElement = null;
5347
6025
  if (disconnectResize) {
5348
6026
  disconnectResize();
5349
6027
  disconnectResize = null;
@@ -5360,8 +6038,8 @@ function createChart(container, spec, options) {
5360
6038
  srTable.parentNode.removeChild(srTable);
5361
6039
  srTable = null;
5362
6040
  }
5363
- container.classList.remove("viz-dark");
5364
- container.classList.remove("viz-root");
6041
+ container.classList.remove("oc-dark");
6042
+ container.classList.remove("oc-root");
5365
6043
  }
5366
6044
  render();
5367
6045
  if (options?.responsive !== false) {
@@ -5380,6 +6058,20 @@ function createChart(container, spec, options) {
5380
6058
  destroy,
5381
6059
  get layout() {
5382
6060
  return currentLayout;
6061
+ },
6062
+ getSelectedElement() {
6063
+ return selectedElement;
6064
+ },
6065
+ select(ref) {
6066
+ if (destroyed) return;
6067
+ selectElement(ref);
6068
+ },
6069
+ deselect() {
6070
+ if (destroyed) return;
6071
+ deselectElement();
6072
+ },
6073
+ get isEditing() {
6074
+ return isTextEditingActive;
5383
6075
  }
5384
6076
  };
5385
6077
  }
@@ -5484,28 +6176,30 @@ function renderTextCell(cell) {
5484
6176
  }
5485
6177
  function renderHeatmapCell(cell) {
5486
6178
  const td = document.createElement("td");
6179
+ td.className = "oc-table-heatmap";
5487
6180
  td.textContent = cell.formattedValue;
5488
6181
  applyCellStyle(td, cell);
5489
6182
  return td;
5490
6183
  }
5491
6184
  function renderCategoryCell(cell) {
5492
6185
  const td = document.createElement("td");
6186
+ td.className = "oc-table-category";
5493
6187
  td.textContent = cell.formattedValue;
5494
6188
  applyCellStyle(td, cell);
5495
6189
  return td;
5496
6190
  }
5497
6191
  function renderBarCell(cell) {
5498
6192
  const td = document.createElement("td");
5499
- td.className = "viz-table-bar";
6193
+ td.className = "oc-table-bar";
5500
6194
  applyCellStyle(td, cell);
5501
6195
  const fill = document.createElement("div");
5502
- fill.className = "viz-table-bar-fill";
6196
+ fill.className = "oc-table-bar-fill";
5503
6197
  fill.style.width = `${Math.round(cell.barWidth * 100)}%`;
5504
6198
  fill.style.left = `${Math.round(cell.barOffset * 100)}%`;
5505
6199
  fill.style.background = cell.barColor;
5506
6200
  td.appendChild(fill);
5507
6201
  const valueSpan = document.createElement("span");
5508
- valueSpan.className = "viz-table-bar-value";
6202
+ valueSpan.className = "oc-table-bar-value";
5509
6203
  valueSpan.textContent = cell.formattedValue;
5510
6204
  td.appendChild(valueSpan);
5511
6205
  return td;
@@ -5532,7 +6226,7 @@ function renderSparklineCell(cell) {
5532
6226
  td.setAttribute("aria-label", trendDescription);
5533
6227
  }
5534
6228
  const wrapper = document.createElement("span");
5535
- wrapper.className = "viz-table-sparkline";
6229
+ wrapper.className = "oc-table-sparkline";
5536
6230
  const svgNS = "http://www.w3.org/2000/svg";
5537
6231
  if (sparklineData.type === "line") {
5538
6232
  const svgH = 28;
@@ -5564,19 +6258,19 @@ function renderSparklineCell(cell) {
5564
6258
  const lastY = yPositions[yPositions.length - 1];
5565
6259
  const dotSize = 5;
5566
6260
  const startDot = document.createElement("span");
5567
- startDot.className = "viz-table-sparkline-dot";
6261
+ startDot.className = "oc-table-sparkline-dot";
5568
6262
  startDot.style.left = "0";
5569
6263
  startDot.style.top = `${firstY - dotSize / 2}px`;
5570
6264
  startDot.style.background = sparklineData.color;
5571
6265
  wrapper.appendChild(startDot);
5572
6266
  const endDot = document.createElement("span");
5573
- endDot.className = "viz-table-sparkline-dot";
6267
+ endDot.className = "oc-table-sparkline-dot";
5574
6268
  endDot.style.right = "0";
5575
6269
  endDot.style.top = `${lastY - dotSize / 2}px`;
5576
6270
  endDot.style.background = sparklineData.color;
5577
6271
  wrapper.appendChild(endDot);
5578
6272
  const labelsRow = document.createElement("span");
5579
- labelsRow.className = "viz-table-sparkline-labels";
6273
+ labelsRow.className = "oc-table-sparkline-labels";
5580
6274
  labelsRow.style.color = sparklineData.color;
5581
6275
  const startLabel = document.createElement("span");
5582
6276
  startLabel.textContent = formatSparklineValue(sparklineData.startValue);
@@ -5653,7 +6347,7 @@ function renderImageCell(cell) {
5653
6347
  const td = document.createElement("td");
5654
6348
  applyCellStyle(td, cell);
5655
6349
  const wrapper = document.createElement("span");
5656
- wrapper.className = `viz-table-image${cell.rounded ? " viz-table-image-rounded" : ""}`;
6350
+ wrapper.className = `oc-table-image${cell.rounded ? " oc-table-image-rounded" : ""}`;
5657
6351
  const img = document.createElement("img");
5658
6352
  img.src = cell.src;
5659
6353
  img.alt = cell.formattedValue || "";
@@ -5668,7 +6362,7 @@ function renderFlagCell(cell) {
5668
6362
  const td = document.createElement("td");
5669
6363
  applyCellStyle(td, cell);
5670
6364
  const span = document.createElement("span");
5671
- span.className = "viz-table-flag";
6365
+ span.className = "oc-table-flag";
5672
6366
  span.setAttribute("role", "img");
5673
6367
  if (cell.countryCode && cell.countryCode.length === 2) {
5674
6368
  const countryName = getCountryName(cell.countryCode);
@@ -5733,9 +6427,9 @@ function attachKeyboardNav(options) {
5733
6427
  return getCellsInRow(rows[0]).length;
5734
6428
  }
5735
6429
  function clearFocusHighlight() {
5736
- const prev = wrapper.querySelector(".viz-table-cell-focus");
6430
+ const prev = wrapper.querySelector(".oc-table-cell-focus");
5737
6431
  if (prev) {
5738
- prev.classList.remove("viz-table-cell-focus");
6432
+ prev.classList.remove("oc-table-cell-focus");
5739
6433
  prev.removeAttribute("id");
5740
6434
  }
5741
6435
  }
@@ -5752,9 +6446,9 @@ function attachKeyboardNav(options) {
5752
6446
  const cells = getCellsInRow(tr);
5753
6447
  const cell = cells[col];
5754
6448
  if (!cell) return;
5755
- const cellId = `viz-cell-${row}-${col}`;
6449
+ const cellId = `oc-cell-${row}-${col}`;
5756
6450
  cell.id = cellId;
5757
- cell.classList.add("viz-table-cell-focus");
6451
+ cell.classList.add("oc-table-cell-focus");
5758
6452
  cell.setAttribute("data-row", String(row));
5759
6453
  cell.setAttribute("data-col", String(col));
5760
6454
  if (tbody) {
@@ -5855,7 +6549,7 @@ function attachKeyboardNav(options) {
5855
6549
  }
5856
6550
  }
5857
6551
  }
5858
- const searchInput = wrapper.querySelector(".viz-table-search input");
6552
+ const searchInput = wrapper.querySelector(".oc-table-search input");
5859
6553
  function handleSearchKeydown(e) {
5860
6554
  if (e.key === "Escape") {
5861
6555
  e.preventDefault();
@@ -5895,22 +6589,27 @@ import { compileTable } from "@opendata-ai/openchart-engine";
5895
6589
 
5896
6590
  // src/table-renderer.ts
5897
6591
  import { BRAND_FONT_SIZE as BRAND_FONT_SIZE3 } from "@opendata-ai/openchart-core";
6592
+ import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
6593
+ var EASE_VAR_MAP2 = {
6594
+ smooth: "var(--oc-ease-smooth)",
6595
+ snappy: "var(--oc-ease-snappy)"
6596
+ };
5898
6597
  var BRAND_URL2 = "https://tryopendata.ai";
5899
6598
  function renderChromeBlock(layout, position) {
5900
6599
  const chrome = layout.chrome;
5901
6600
  if (position === "header") {
5902
6601
  if (!chrome.title && !chrome.subtitle) return null;
5903
6602
  const div2 = document.createElement("div");
5904
- div2.className = "viz-chrome";
6603
+ div2.className = "oc-chrome";
5905
6604
  if (chrome.title) {
5906
6605
  const h = document.createElement("div");
5907
- h.className = "viz-table-title";
6606
+ h.className = "oc-table-title";
5908
6607
  h.textContent = chrome.title.text;
5909
6608
  div2.appendChild(h);
5910
6609
  }
5911
6610
  if (chrome.subtitle) {
5912
6611
  const sub = document.createElement("div");
5913
- sub.className = "viz-table-subtitle";
6612
+ sub.className = "oc-table-subtitle";
5914
6613
  sub.textContent = chrome.subtitle.text;
5915
6614
  div2.appendChild(sub);
5916
6615
  }
@@ -5918,16 +6617,16 @@ function renderChromeBlock(layout, position) {
5918
6617
  }
5919
6618
  if (!chrome.source && !chrome.footer) return null;
5920
6619
  const div = document.createElement("div");
5921
- div.className = "viz-chrome viz-chrome-footer";
6620
+ div.className = "oc-chrome oc-chrome-footer";
5922
6621
  if (chrome.source) {
5923
6622
  const src = document.createElement("div");
5924
- src.className = "viz-table-source";
6623
+ src.className = "oc-table-source";
5925
6624
  src.textContent = chrome.source.text;
5926
6625
  div.appendChild(src);
5927
6626
  }
5928
6627
  if (chrome.footer) {
5929
6628
  const foot = document.createElement("div");
5930
- foot.className = "viz-table-footer-text";
6629
+ foot.className = "oc-table-footer-text";
5931
6630
  foot.textContent = chrome.footer.text;
5932
6631
  div.appendChild(foot);
5933
6632
  }
@@ -5953,7 +6652,7 @@ function renderThead(columns, sort) {
5953
6652
  th.appendChild(labelSpan);
5954
6653
  if (col.sortable) {
5955
6654
  const btn = document.createElement("button");
5956
- btn.className = "viz-table-sort-btn";
6655
+ btn.className = "oc-table-sort-btn";
5957
6656
  btn.setAttribute("aria-label", `Sort by ${col.label}`);
5958
6657
  btn.setAttribute("data-sort-column", col.key);
5959
6658
  btn.type = "button";
@@ -5971,6 +6670,7 @@ function renderTbody(rows, columns) {
5971
6670
  const tr = document.createElement("tr");
5972
6671
  tr.setAttribute("role", "row");
5973
6672
  tr.setAttribute("data-row-id", row.id);
6673
+ tr.style.setProperty("--oc-row-index", String(r));
5974
6674
  for (let c2 = 0; c2 < columns.length; c2++) {
5975
6675
  const cell = row.cells[c2];
5976
6676
  if (!cell) continue;
@@ -5986,7 +6686,7 @@ function renderTbody(rows, columns) {
5986
6686
  function renderSearchBar(layout) {
5987
6687
  if (!layout.search.enabled) return null;
5988
6688
  const div = document.createElement("div");
5989
- div.className = "viz-table-search";
6689
+ div.className = "oc-table-search";
5990
6690
  const input = document.createElement("input");
5991
6691
  input.type = "search";
5992
6692
  input.placeholder = layout.search.placeholder;
@@ -5999,9 +6699,9 @@ function renderPagination(layout) {
5999
6699
  if (!layout.pagination) return null;
6000
6700
  const { page, pageSize, totalRows, totalPages } = layout.pagination;
6001
6701
  const div = document.createElement("div");
6002
- div.className = "viz-table-pagination";
6702
+ div.className = "oc-table-pagination";
6003
6703
  const info = document.createElement("span");
6004
- info.className = "viz-table-pagination-info";
6704
+ info.className = "oc-table-pagination-info";
6005
6705
  if (totalRows === 0) {
6006
6706
  info.textContent = "No results";
6007
6707
  } else {
@@ -6011,7 +6711,7 @@ function renderPagination(layout) {
6011
6711
  }
6012
6712
  div.appendChild(info);
6013
6713
  const btnGroup = document.createElement("span");
6014
- btnGroup.className = "viz-table-pagination-btns";
6714
+ btnGroup.className = "oc-table-pagination-btns";
6015
6715
  const prevBtn = document.createElement("button");
6016
6716
  prevBtn.setAttribute("aria-label", "Previous page");
6017
6717
  prevBtn.setAttribute("data-page-action", "prev");
@@ -6029,49 +6729,49 @@ function renderPagination(layout) {
6029
6729
  }
6030
6730
  function renderEmptyState(message) {
6031
6731
  const div = document.createElement("div");
6032
- div.className = "viz-table-empty";
6732
+ div.className = "oc-table-empty";
6033
6733
  div.setAttribute("aria-live", "polite");
6034
6734
  div.textContent = message;
6035
6735
  return div;
6036
6736
  }
6037
- function renderTable(layout, container) {
6737
+ function renderTable(layout, container, opts) {
6038
6738
  const wrapper = document.createElement("div");
6039
- wrapper.className = "viz-table-wrapper";
6739
+ wrapper.className = "oc-table-wrapper";
6040
6740
  const { theme, chrome } = layout;
6041
6741
  if (theme) {
6042
6742
  const s = wrapper.style;
6043
- s.setProperty("--viz-bg", theme.colors.background);
6044
- s.setProperty("--viz-text", theme.colors.text);
6045
- s.setProperty("--viz-text-secondary", theme.colors.axis ?? theme.colors.text);
6046
- s.setProperty("--viz-text-muted", theme.colors.axis ?? theme.colors.text);
6047
- s.setProperty("--viz-gridline", theme.colors.gridline);
6048
- s.setProperty("--viz-border", theme.colors.gridline);
6049
- s.setProperty("--viz-font-family", theme.fonts.family);
6743
+ s.setProperty("--oc-bg", theme.colors.background);
6744
+ s.setProperty("--oc-text", theme.colors.text);
6745
+ s.setProperty("--oc-text-secondary", theme.colors.axis ?? theme.colors.text);
6746
+ s.setProperty("--oc-text-muted", theme.colors.axis ?? theme.colors.text);
6747
+ s.setProperty("--oc-gridline", theme.colors.gridline);
6748
+ s.setProperty("--oc-border", theme.colors.gridline);
6749
+ s.setProperty("--oc-font-family", theme.fonts.family);
6050
6750
  s.fontFamily = theme.fonts.family;
6051
6751
  }
6052
6752
  {
6053
6753
  const s = wrapper.style;
6054
6754
  if (chrome.title) {
6055
- s.setProperty("--viz-title-computed-size", `${chrome.title.style.fontSize}px`);
6056
- s.setProperty("--viz-title-computed-weight", String(chrome.title.style.fontWeight));
6057
- s.setProperty("--viz-title-computed-color", chrome.title.style.fill);
6755
+ s.setProperty("--oc-title-computed-size", `${chrome.title.style.fontSize}px`);
6756
+ s.setProperty("--oc-title-computed-weight", String(chrome.title.style.fontWeight));
6757
+ s.setProperty("--oc-title-computed-color", chrome.title.style.fill);
6058
6758
  }
6059
6759
  if (chrome.subtitle) {
6060
- s.setProperty("--viz-subtitle-computed-size", `${chrome.subtitle.style.fontSize}px`);
6061
- s.setProperty("--viz-subtitle-computed-weight", String(chrome.subtitle.style.fontWeight));
6062
- s.setProperty("--viz-subtitle-computed-color", chrome.subtitle.style.fill);
6760
+ s.setProperty("--oc-subtitle-computed-size", `${chrome.subtitle.style.fontSize}px`);
6761
+ s.setProperty("--oc-subtitle-computed-weight", String(chrome.subtitle.style.fontWeight));
6762
+ s.setProperty("--oc-subtitle-computed-color", chrome.subtitle.style.fill);
6063
6763
  }
6064
6764
  if (chrome.source) {
6065
- s.setProperty("--viz-source-computed-size", `${chrome.source.style.fontSize}px`);
6066
- s.setProperty("--viz-source-computed-color", chrome.source.style.fill);
6765
+ s.setProperty("--oc-source-computed-size", `${chrome.source.style.fontSize}px`);
6766
+ s.setProperty("--oc-source-computed-color", chrome.source.style.fill);
6067
6767
  }
6068
6768
  if (chrome.footer) {
6069
- s.setProperty("--viz-footer-computed-size", `${chrome.footer.style.fontSize}px`);
6070
- s.setProperty("--viz-footer-computed-color", chrome.footer.style.fill);
6769
+ s.setProperty("--oc-footer-computed-size", `${chrome.footer.style.fontSize}px`);
6770
+ s.setProperty("--oc-footer-computed-color", chrome.footer.style.fill);
6071
6771
  }
6072
6772
  }
6073
6773
  if (layout.compact) {
6074
- wrapper.classList.add("viz-table--compact");
6774
+ wrapper.classList.add("oc-table--compact");
6075
6775
  }
6076
6776
  const headerChrome = renderChromeBlock(layout, "header");
6077
6777
  if (headerChrome) {
@@ -6086,15 +6786,15 @@ function renderTable(layout, container) {
6086
6786
  wrapper.appendChild(renderEmptyState(message));
6087
6787
  } else {
6088
6788
  const scroll = document.createElement("div");
6089
- scroll.className = "viz-table-scroll";
6789
+ scroll.className = "oc-table-scroll";
6090
6790
  const table = document.createElement("table");
6091
6791
  table.setAttribute("role", "grid");
6092
6792
  table.setAttribute("aria-label", layout.a11y.caption);
6093
6793
  if (layout.stickyFirstColumn) {
6094
- table.classList.add("viz-table--sticky");
6794
+ table.classList.add("oc-table--sticky");
6095
6795
  }
6096
6796
  const caption = document.createElement("caption");
6097
- caption.className = "viz-sr-only";
6797
+ caption.className = "oc-sr-only";
6098
6798
  caption.style.position = "absolute";
6099
6799
  caption.style.width = "1px";
6100
6800
  caption.style.height = "1px";
@@ -6120,7 +6820,7 @@ function renderTable(layout, container) {
6120
6820
  wrapper.appendChild(footerChrome);
6121
6821
  }
6122
6822
  const liveRegion = document.createElement("div");
6123
- liveRegion.className = "viz-table-live-region viz-sr-only";
6823
+ liveRegion.className = "oc-table-live-region oc-sr-only";
6124
6824
  liveRegion.style.position = "absolute";
6125
6825
  liveRegion.style.width = "1px";
6126
6826
  liveRegion.style.height = "1px";
@@ -6136,7 +6836,7 @@ function renderTable(layout, container) {
6136
6836
  wrapper.appendChild(liveRegion);
6137
6837
  const brandColor = theme ? theme.colors.axis : "#999999";
6138
6838
  const brand = document.createElement("div");
6139
- brand.className = "viz-table-ref";
6839
+ brand.className = "oc-table-ref";
6140
6840
  brand.style.cssText = "text-align: right; padding: 4px 8px;";
6141
6841
  const brandLink = document.createElement("a");
6142
6842
  brandLink.href = BRAND_URL2;
@@ -6146,6 +6846,16 @@ function renderTable(layout, container) {
6146
6846
  brandLink.textContent = "OpenData";
6147
6847
  brand.appendChild(brandLink);
6148
6848
  wrapper.appendChild(brand);
6849
+ if (opts?.animate && layout.animation?.enabled) {
6850
+ const anim = layout.animation;
6851
+ const rowCount = layout.rows.length;
6852
+ const stagger = clampStaggerDelay2(anim.staggerDelay, rowCount);
6853
+ const s = wrapper.style;
6854
+ s.setProperty("--oc-animation-duration", `${anim.duration}ms`);
6855
+ s.setProperty("--oc-animation-stagger", `${stagger}ms`);
6856
+ s.setProperty("--oc-animation-ease", EASE_VAR_MAP2[anim.ease] || EASE_VAR_MAP2.smooth);
6857
+ wrapper.classList.add("oc-animate");
6858
+ }
6149
6859
  container.appendChild(wrapper);
6150
6860
  return wrapper;
6151
6861
  }
@@ -6180,6 +6890,8 @@ function createTable(container, spec, options) {
6180
6890
  let wrapperElement = null;
6181
6891
  let disconnectResize = null;
6182
6892
  let cleanupKeyboard = null;
6893
+ let cleanupAnimations = null;
6894
+ let isFirstRender = true;
6183
6895
  let destroyed = false;
6184
6896
  const internalState = {
6185
6897
  sort: null,
@@ -6239,7 +6951,7 @@ function createTable(container, spec, options) {
6239
6951
  }
6240
6952
  function announce(message) {
6241
6953
  if (!wrapperElement) return;
6242
- const liveRegion = wrapperElement.querySelector(".viz-table-live-region");
6954
+ const liveRegion = wrapperElement.querySelector(".oc-table-live-region");
6243
6955
  if (liveRegion) {
6244
6956
  liveRegion.textContent = message;
6245
6957
  }
@@ -6249,14 +6961,18 @@ function createTable(container, spec, options) {
6249
6961
  const { width } = getContainerDimensions();
6250
6962
  const bp = getBreakpoint(width);
6251
6963
  if (bp === "compact" || bp === "medium") {
6252
- wrapperElement.classList.add("viz-table--compact");
6964
+ wrapperElement.classList.add("oc-table--compact");
6253
6965
  } else if (!currentLayout?.compact) {
6254
- wrapperElement.classList.remove("viz-table--compact");
6966
+ wrapperElement.classList.remove("oc-table--compact");
6255
6967
  }
6256
6968
  }
6257
6969
  function render() {
6258
6970
  if (destroyed) return;
6259
6971
  try {
6972
+ if (cleanupAnimations) {
6973
+ cleanupAnimations();
6974
+ cleanupAnimations = null;
6975
+ }
6260
6976
  if (cleanupKeyboard) {
6261
6977
  cleanupKeyboard();
6262
6978
  cleanupKeyboard = null;
@@ -6266,16 +6982,23 @@ function createTable(container, spec, options) {
6266
6982
  wrapperElement = null;
6267
6983
  }
6268
6984
  currentLayout = compile();
6269
- wrapperElement = renderTable(currentLayout, container);
6985
+ const shouldAnimate = isFirstRender && !!currentLayout.animation?.enabled;
6986
+ wrapperElement = renderTable(currentLayout, container, { animate: shouldAnimate });
6987
+ if (shouldAnimate && wrapperElement) {
6988
+ cleanupAnimations = setupTableAnimationCleanup(wrapperElement);
6989
+ }
6990
+ if (isFirstRender) {
6991
+ isFirstRender = false;
6992
+ }
6270
6993
  const isDark = resolveDarkMode3(options?.darkMode);
6271
6994
  if (isDark) {
6272
- container.classList.add("viz-dark");
6995
+ container.classList.add("oc-dark");
6273
6996
  } else {
6274
- container.classList.remove("viz-dark");
6997
+ container.classList.remove("oc-dark");
6275
6998
  }
6276
6999
  applyBreakpointClass();
6277
7000
  if (options?.onRowClick) {
6278
- wrapperElement.classList.add("viz-table--clickable");
7001
+ wrapperElement.classList.add("oc-table--clickable");
6279
7002
  }
6280
7003
  wireEvents();
6281
7004
  if (wrapperElement) {
@@ -6315,7 +7038,7 @@ function createTable(container, spec, options) {
6315
7038
  btn.addEventListener("click", handleSortClick);
6316
7039
  }
6317
7040
  const searchInput = wrapperElement.querySelector(
6318
- ".viz-table-search input"
7041
+ ".oc-table-search input"
6319
7042
  );
6320
7043
  if (searchInput) {
6321
7044
  searchInput.addEventListener("input", handleSearchInput);
@@ -6391,7 +7114,7 @@ function createTable(container, spec, options) {
6391
7114
  function rerender() {
6392
7115
  if (destroyed) return;
6393
7116
  const searchInput = wrapperElement?.querySelector(
6394
- ".viz-table-search input"
7117
+ ".oc-table-search input"
6395
7118
  );
6396
7119
  const hadFocus = searchInput && document.activeElement === searchInput;
6397
7120
  const selectionStart = searchInput?.selectionStart ?? 0;
@@ -6399,7 +7122,7 @@ function createTable(container, spec, options) {
6399
7122
  render();
6400
7123
  if (hadFocus) {
6401
7124
  const newInput = wrapperElement?.querySelector(
6402
- ".viz-table-search input"
7125
+ ".oc-table-search input"
6403
7126
  );
6404
7127
  if (newInput) {
6405
7128
  newInput.focus();
@@ -6414,6 +7137,7 @@ function createTable(container, spec, options) {
6414
7137
  }
6415
7138
  function resize() {
6416
7139
  if (destroyed) return;
7140
+ if (cleanupAnimations) return;
6417
7141
  render();
6418
7142
  }
6419
7143
  function doExport(format) {
@@ -6450,6 +7174,10 @@ function createTable(container, spec, options) {
6450
7174
  function destroy() {
6451
7175
  if (destroyed) return;
6452
7176
  destroyed = true;
7177
+ if (cleanupAnimations) {
7178
+ cleanupAnimations();
7179
+ cleanupAnimations = null;
7180
+ }
6453
7181
  if (cleanupKeyboard) {
6454
7182
  cleanupKeyboard();
6455
7183
  cleanupKeyboard = null;
@@ -6470,7 +7198,7 @@ function createTable(container, spec, options) {
6470
7198
  wrapperElement.parentNode.removeChild(wrapperElement);
6471
7199
  wrapperElement = null;
6472
7200
  }
6473
- container.classList.remove("viz-dark");
7201
+ container.classList.remove("oc-dark");
6474
7202
  }
6475
7203
  render();
6476
7204
  if (options?.responsive !== false) {
@@ -6500,6 +7228,7 @@ export {
6500
7228
  createGraph,
6501
7229
  createSimulationWorker,
6502
7230
  createTable,
7231
+ createTextEditOverlay,
6503
7232
  createTooltipManager,
6504
7233
  exportCSV,
6505
7234
  exportJPG,