@opendata-ai/openchart-vanilla 6.4.1 → 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.
@@ -199,7 +199,7 @@ describe('createTable', () => {
199
199
  const spec = makeSpec({ search: true });
200
200
  const table = createTable(container, spec);
201
201
 
202
- const input = container.querySelector('.viz-table-search input') as HTMLInputElement;
202
+ const input = container.querySelector('.oc-table-search input') as HTMLInputElement;
203
203
  expect(input).not.toBeNull();
204
204
 
205
205
  // Type in a search query
@@ -223,7 +223,7 @@ describe('createTable', () => {
223
223
  const table = createTable(container, paginatedSpec);
224
224
 
225
225
  // Should show page 1 of 5 (10 per page, 50 total)
226
- const info = container.querySelector('.viz-table-pagination-info');
226
+ const info = container.querySelector('.oc-table-pagination-info');
227
227
  expect(info?.textContent).toContain('Showing 1-10 of 50');
228
228
 
229
229
  const rows = container.querySelectorAll('tbody tr');
@@ -234,7 +234,7 @@ describe('createTable', () => {
234
234
  expect(nextBtn).not.toBeNull();
235
235
  nextBtn.dispatchEvent(new Event('click', { bubbles: true }));
236
236
 
237
- const infoAfter = container.querySelector('.viz-table-pagination-info');
237
+ const infoAfter = container.querySelector('.oc-table-pagination-info');
238
238
  expect(infoAfter?.textContent).toContain('Showing 11-20 of 50');
239
239
 
240
240
  // Previous button should be enabled
@@ -248,7 +248,7 @@ describe('createTable', () => {
248
248
  const table = createTable(container, stickySpec);
249
249
 
250
250
  const tableEl = container.querySelector('table');
251
- expect(tableEl?.classList.contains('viz-table--sticky')).toBe(true);
251
+ expect(tableEl?.classList.contains('oc-table--sticky')).toBe(true);
252
252
 
253
253
  table.destroy();
254
254
  });
@@ -272,7 +272,7 @@ describe('createTable', () => {
272
272
  const table = createTable(container, sparklineSpec);
273
273
 
274
274
  // Sparkline cells should be rendered with the sparkline class
275
- const sparklineCells = container.querySelectorAll('.viz-table-sparkline');
275
+ const sparklineCells = container.querySelectorAll('.oc-table-sparkline');
276
276
  expect(sparklineCells.length).toBeGreaterThan(0);
277
277
 
278
278
  const svg = sparklineCells[0]?.querySelector('svg');
@@ -288,11 +288,11 @@ describe('createTable', () => {
288
288
  it('bar cells have proportional fill div', () => {
289
289
  const table = createTable(container, barSpec);
290
290
 
291
- const barFills = container.querySelectorAll('.viz-table-bar-fill');
291
+ const barFills = container.querySelectorAll('.oc-table-bar-fill');
292
292
  expect(barFills.length).toBe(3);
293
293
 
294
294
  // The bar values should have width proportional to their data
295
- const barValues = container.querySelectorAll('.viz-table-bar-value');
295
+ const barValues = container.querySelectorAll('.oc-table-bar-value');
296
296
  expect(barValues.length).toBe(3);
297
297
 
298
298
  table.destroy();
@@ -331,14 +331,14 @@ describe('createTable', () => {
331
331
  const table = createTable(container, spec);
332
332
 
333
333
  // Search for something that doesn't match any data
334
- const input = container.querySelector('.viz-table-search input') as HTMLInputElement;
334
+ const input = container.querySelector('.oc-table-search input') as HTMLInputElement;
335
335
  input.value = 'zzzznonexistent';
336
336
  input.dispatchEvent(new Event('input', { bubbles: true }));
337
337
 
338
338
  // Advance past the 200ms debounce
339
339
  vi.advanceTimersByTime(200);
340
340
 
341
- const empty = container.querySelector('.viz-table-empty');
341
+ const empty = container.querySelector('.oc-table-empty');
342
342
  expect(empty).not.toBeNull();
343
343
  expect(empty?.textContent).toBe('No results found');
344
344
 
@@ -421,21 +421,21 @@ describe('createTable', () => {
421
421
  table.destroy();
422
422
  });
423
423
 
424
- it('compact mode applies viz-table--compact class', () => {
424
+ it('compact mode applies oc-table--compact class', () => {
425
425
  const spec = makeSpec({ compact: true });
426
426
  const table = createTable(container, spec);
427
427
 
428
- const wrapper = container.querySelector('.viz-table-wrapper');
429
- expect(wrapper?.classList.contains('viz-table--compact')).toBe(true);
428
+ const wrapper = container.querySelector('.oc-table-wrapper');
429
+ expect(wrapper?.classList.contains('oc-table--compact')).toBe(true);
430
430
 
431
431
  table.destroy();
432
432
  });
433
433
 
434
- it('dark mode applies viz-dark class', () => {
434
+ it('dark mode applies oc-dark class', () => {
435
435
  const spec = makeSpec();
436
436
  const table = createTable(container, spec, { darkMode: 'force' });
437
437
 
438
- expect(container.classList.contains('viz-dark')).toBe(true);
438
+ expect(container.classList.contains('oc-dark')).toBe(true);
439
439
 
440
440
  table.destroy();
441
441
  });
@@ -445,8 +445,8 @@ describe('createTable', () => {
445
445
  const spec = makeSpec();
446
446
  const table = createTable(container, spec, { onRowClick: onClick });
447
447
 
448
- const wrapper = container.querySelector('.viz-table-wrapper');
449
- expect(wrapper?.classList.contains('viz-table--clickable')).toBe(true);
448
+ const wrapper = container.querySelector('.oc-table-wrapper');
449
+ expect(wrapper?.classList.contains('oc-table--clickable')).toBe(true);
450
450
 
451
451
  // Click first row
452
452
  const firstRow = container.querySelector('tbody tr');
@@ -462,7 +462,7 @@ describe('createTable', () => {
462
462
  const spec = makeSpec();
463
463
  const table = createTable(container, spec);
464
464
 
465
- const title = container.querySelector('.viz-table-title');
465
+ const title = container.querySelector('.oc-table-title');
466
466
  expect(title).not.toBeNull();
467
467
  expect(title?.textContent).toBe('People');
468
468
 
@@ -481,4 +481,125 @@ describe('createTable', () => {
481
481
 
482
482
  table.destroy();
483
483
  });
484
+
485
+ // -------------------------------------------------------------------------
486
+ // Animation lifecycle
487
+ // -------------------------------------------------------------------------
488
+
489
+ describe('animation', () => {
490
+ it('adds oc-animate class on first render when animation is enabled', () => {
491
+ const spec = makeSpec({ animation: true });
492
+ const table = createTable(container, spec);
493
+
494
+ const wrapper = container.querySelector('.oc-table-wrapper');
495
+ expect(wrapper?.classList.contains('oc-animate')).toBe(true);
496
+
497
+ table.destroy();
498
+ });
499
+
500
+ it('does not add oc-animate when animation is not set', () => {
501
+ const table = createTable(container, makeSpec());
502
+
503
+ const wrapper = container.querySelector('.oc-table-wrapper');
504
+ expect(wrapper?.classList.contains('oc-animate')).toBe(false);
505
+
506
+ table.destroy();
507
+ });
508
+
509
+ it('sets --oc-animation-duration CSS custom property', () => {
510
+ const spec = makeSpec({ animation: { enter: { duration: 800 } } });
511
+ const table = createTable(container, spec);
512
+
513
+ const wrapper = container.querySelector('.oc-table-wrapper') as HTMLElement;
514
+ expect(wrapper.style.getPropertyValue('--oc-animation-duration')).toBe('800ms');
515
+
516
+ table.destroy();
517
+ });
518
+
519
+ it('sets --oc-animation-stagger CSS custom property', () => {
520
+ const spec = makeSpec({ animation: true });
521
+ const table = createTable(container, spec);
522
+
523
+ const wrapper = container.querySelector('.oc-table-wrapper') as HTMLElement;
524
+ const stagger = wrapper.style.getPropertyValue('--oc-animation-stagger');
525
+ expect(stagger).toMatch(/^\d+ms$/);
526
+
527
+ table.destroy();
528
+ });
529
+
530
+ it('stamps --oc-row-index on each tbody tr', () => {
531
+ const spec = makeSpec({ animation: true });
532
+ const table = createTable(container, spec);
533
+
534
+ const rows = container.querySelectorAll('tbody tr');
535
+ for (let i = 0; i < rows.length; i++) {
536
+ const row = rows[i] as HTMLElement;
537
+ expect(row.style.getPropertyValue('--oc-row-index')).toBe(String(i));
538
+ }
539
+
540
+ table.destroy();
541
+ });
542
+
543
+ it('does not re-animate on sort (only first render)', () => {
544
+ const spec = makeSpec({ animation: true });
545
+ const table = createTable(container, spec);
546
+
547
+ // Trigger sort via click
548
+ const sortBtn = container.querySelector('[data-sort-column]');
549
+ if (sortBtn) {
550
+ sortBtn.dispatchEvent(new Event('click', { bubbles: true }));
551
+ }
552
+
553
+ const wrapper = container.querySelector('.oc-table-wrapper');
554
+ expect(wrapper?.classList.contains('oc-animate')).toBe(false);
555
+
556
+ table.destroy();
557
+ });
558
+
559
+ it('does not re-animate on update()', () => {
560
+ const spec = makeSpec({ animation: true });
561
+ const table = createTable(container, spec);
562
+
563
+ table.update(makeSpec({ animation: true }));
564
+
565
+ const wrapper = container.querySelector('.oc-table-wrapper');
566
+ expect(wrapper?.classList.contains('oc-animate')).toBe(false);
567
+
568
+ table.destroy();
569
+ });
570
+
571
+ it('removes oc-animate after cleanup timeout', () => {
572
+ vi.useFakeTimers();
573
+ try {
574
+ const spec = makeSpec({ animation: true });
575
+ const table = createTable(container, spec);
576
+
577
+ const wrapper = container.querySelector('.oc-table-wrapper');
578
+ expect(wrapper?.classList.contains('oc-animate')).toBe(true);
579
+
580
+ // Advance past total animation time
581
+ vi.advanceTimersByTime(5000);
582
+
583
+ expect(wrapper?.classList.contains('oc-animate')).toBe(false);
584
+
585
+ table.destroy();
586
+ } finally {
587
+ vi.useRealTimers();
588
+ }
589
+ });
590
+
591
+ it('destroy cancels animation cleanup without error', () => {
592
+ vi.useFakeTimers();
593
+ try {
594
+ const spec = makeSpec({ animation: true });
595
+ const table = createTable(container, spec);
596
+ table.destroy();
597
+
598
+ // Should not throw after timer fires
599
+ vi.advanceTimersByTime(5000);
600
+ } finally {
601
+ vi.useRealTimers();
602
+ }
603
+ });
604
+ });
484
605
  });
@@ -56,7 +56,7 @@ describe('createTooltipManager lifecycle', () => {
56
56
  const container = createContainer();
57
57
  createTooltipManager(container);
58
58
 
59
- const tooltip = container.querySelector('.viz-tooltip');
59
+ const tooltip = container.querySelector('.oc-tooltip');
60
60
  expect(tooltip).not.toBeNull();
61
61
  expect(tooltip!.getAttribute('role')).toBe('tooltip');
62
62
  });
@@ -67,7 +67,7 @@ describe('createTooltipManager lifecycle', () => {
67
67
 
68
68
  manager.show({ title: 'Point A', fields: [{ label: 'Value', value: '42' }] }, 100, 100);
69
69
 
70
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
70
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
71
71
  expect(tooltip.style.display).toBe('block');
72
72
  });
73
73
 
@@ -87,7 +87,7 @@ describe('createTooltipManager lifecycle', () => {
87
87
  100,
88
88
  );
89
89
 
90
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
90
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
91
91
  expect(tooltip.innerHTML).toContain('2021-Q1');
92
92
  expect(tooltip.innerHTML).toContain('Revenue');
93
93
  expect(tooltip.innerHTML).toContain('$1.2M');
@@ -108,7 +108,7 @@ describe('createTooltipManager lifecycle', () => {
108
108
  50,
109
109
  );
110
110
 
111
- const dot = container.querySelector('.viz-tooltip-dot') as HTMLElement;
111
+ const dot = container.querySelector('.oc-tooltip-dot') as HTMLElement;
112
112
  expect(dot).not.toBeNull();
113
113
  expect(dot.style.background).toBe('#ff0000');
114
114
  });
@@ -120,7 +120,7 @@ describe('createTooltipManager lifecycle', () => {
120
120
  manager.show({ title: 'Test', fields: [{ label: 'V', value: '1' }] }, 100, 100);
121
121
  manager.hide();
122
122
 
123
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
123
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
124
124
  expect(tooltip.style.display).toBe('none');
125
125
  });
126
126
 
@@ -129,11 +129,11 @@ describe('createTooltipManager lifecycle', () => {
129
129
  const manager = createTooltipManager(container);
130
130
 
131
131
  // Verify it exists
132
- expect(container.querySelector('.viz-tooltip')).not.toBeNull();
132
+ expect(container.querySelector('.oc-tooltip')).not.toBeNull();
133
133
 
134
134
  manager.destroy();
135
135
 
136
- expect(container.querySelector('.viz-tooltip')).toBeNull();
136
+ expect(container.querySelector('.oc-tooltip')).toBeNull();
137
137
  });
138
138
 
139
139
  it('show() updates content when called again', () => {
@@ -142,12 +142,12 @@ describe('createTooltipManager lifecycle', () => {
142
142
 
143
143
  manager.show({ title: 'First', fields: [{ label: 'A', value: '1' }] }, 50, 50);
144
144
 
145
- let tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
145
+ let tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
146
146
  expect(tooltip.innerHTML).toContain('First');
147
147
 
148
148
  manager.show({ title: 'Second', fields: [{ label: 'B', value: '2' }] }, 100, 100);
149
149
 
150
- tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
150
+ tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
151
151
  expect(tooltip.innerHTML).toContain('Second');
152
152
  // First content should be replaced
153
153
  expect(tooltip.innerHTML).not.toContain('First');
@@ -190,7 +190,7 @@ describe('tooltip positioning', () => {
190
190
  expect(rect.height).toBe(0);
191
191
 
192
192
  // Position should be applied from computePosition result
193
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
193
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
194
194
  expect(tooltip.style.left).toContain('px');
195
195
  expect(tooltip.style.top).toContain('px');
196
196
 
@@ -239,7 +239,7 @@ describe('tooltip positioning', () => {
239
239
  });
240
240
  await flushPositioning();
241
241
 
242
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
242
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
243
243
  const posAfterSecond = tooltip.style.left;
244
244
 
245
245
  // Now resolve the first (stale) - should be discarded
@@ -297,7 +297,7 @@ describe('tooltip content escaping', () => {
297
297
  50,
298
298
  );
299
299
 
300
- const tooltip = container.querySelector('.viz-tooltip') as HTMLElement;
300
+ const tooltip = container.querySelector('.oc-tooltip') as HTMLElement;
301
301
  // Should not contain raw HTML tags - the <script> should be escaped
302
302
  expect(tooltip.innerHTML).not.toContain('<script>');
303
303
  // Should contain escaped versions of angle brackets and ampersands
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Animation runtime for entrance animations.
3
+ *
4
+ * All animations are CSS-driven (keyframes + clip-path + transforms + opacity).
5
+ * This module handles lifecycle: cleanup after completion, cancellation on update.
6
+ * No WAAPI needed since clip-path handles line/area drawing.
7
+ */
8
+
9
+ /**
10
+ * Cancel entrance animations and clean up.
11
+ * Called when update() is invoked during animation, or on destroy.
12
+ */
13
+ export function cancelAnimations(svg: SVGElement | null): void {
14
+ if (svg) {
15
+ svg.classList.remove('oc-animate');
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Set up animation cleanup that removes oc-animate after all animations complete.
21
+ *
22
+ * Uses the computed total animation time (duration + stagger * elementCount + annotation delay)
23
+ * rather than animationend events, because animationend fires per-element and the first
24
+ * element to finish would prematurely kill staggered animations still in progress.
25
+ */
26
+ export function setupAnimationCleanup(svg: SVGElement): () => void {
27
+ // Read the animation timing from the CSS custom properties set by the renderer
28
+ const style = svg.style;
29
+ const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 600;
30
+ const stagger = parseFloat(style.getPropertyValue('--oc-animation-stagger')) || 0;
31
+ const annotationDelay = parseFloat(style.getPropertyValue('--oc-annotation-delay')) || 200;
32
+
33
+ // Count animated elements to compute total stagger span
34
+ const animatedElements = svg.querySelectorAll('[data-animation-index]').length;
35
+ const totalStagger = stagger * Math.max(0, animatedElements - 1);
36
+
37
+ // Total time: last element's stagger delay + its duration + annotation delay + buffer
38
+ const totalTime = totalStagger + duration + annotationDelay + 500;
39
+
40
+ const timer = setTimeout(() => {
41
+ svg.classList.remove('oc-animate');
42
+ }, totalTime);
43
+
44
+ return () => {
45
+ clearTimeout(timer);
46
+ cancelAnimations(svg);
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Set up animation cleanup for table entrance animations.
52
+ *
53
+ * Same timeout-based approach as chart animations: compute total time from
54
+ * CSS custom properties and row count, then remove oc-animate after completion.
55
+ */
56
+ export function setupTableAnimationCleanup(wrapper: HTMLElement): () => void {
57
+ const style = wrapper.style;
58
+ const duration = parseFloat(style.getPropertyValue('--oc-animation-duration')) || 500;
59
+ const stagger = parseFloat(style.getPropertyValue('--oc-animation-stagger')) || 0;
60
+
61
+ const rows = wrapper.querySelectorAll('tbody tr').length;
62
+ const totalStagger = stagger * Math.max(0, rows - 1);
63
+
64
+ // Total: last row stagger + duration + buffer
65
+ const totalTime = totalStagger + duration + 300;
66
+
67
+ const timer = setTimeout(() => {
68
+ wrapper.classList.remove('oc-animate');
69
+ }, totalTime);
70
+
71
+ return () => {
72
+ clearTimeout(timer);
73
+ wrapper.classList.remove('oc-animate');
74
+ };
75
+ }
@@ -91,30 +91,30 @@ describe('createGraph', () => {
91
91
  const graph = createGraph(container, basicSpec);
92
92
 
93
93
  // Wrapper
94
- const wrapper = container.querySelector('.viz-graph-wrapper');
94
+ const wrapper = container.querySelector('.oc-graph-wrapper');
95
95
  expect(wrapper).not.toBeNull();
96
96
 
97
97
  // Canvas
98
- const canvas = container.querySelector('.viz-graph-canvas');
98
+ const canvas = container.querySelector('.oc-graph-canvas');
99
99
  expect(canvas).not.toBeNull();
100
100
  expect(canvas?.tagName.toLowerCase()).toBe('canvas');
101
101
 
102
102
  // Chrome
103
- const chrome = container.querySelector('.viz-graph-chrome');
103
+ const chrome = container.querySelector('.oc-graph-chrome');
104
104
  expect(chrome).not.toBeNull();
105
105
 
106
106
  // Title
107
- const title = container.querySelector('.viz-title');
107
+ const title = container.querySelector('.oc-title');
108
108
  expect(title).not.toBeNull();
109
109
  expect(title?.textContent).toBe('Test Graph');
110
110
 
111
111
  // Subtitle
112
- const subtitle = container.querySelector('.viz-subtitle');
112
+ const subtitle = container.querySelector('.oc-subtitle');
113
113
  expect(subtitle).not.toBeNull();
114
114
  expect(subtitle?.textContent).toBe('A simple test graph');
115
115
 
116
116
  // Legend exists (even if hidden for non-community graphs)
117
- const legend = container.querySelector('.viz-graph-legend');
117
+ const legend = container.querySelector('.oc-graph-legend');
118
118
  expect(legend).not.toBeNull();
119
119
 
120
120
  graph.destroy();
@@ -124,12 +124,12 @@ describe('createGraph', () => {
124
124
  container = makeContainer();
125
125
  const graph = createGraph(container, basicSpec);
126
126
 
127
- expect(container.querySelector('.viz-graph-wrapper')).not.toBeNull();
127
+ expect(container.querySelector('.oc-graph-wrapper')).not.toBeNull();
128
128
 
129
129
  graph.destroy();
130
130
 
131
- expect(container.querySelector('.viz-graph-wrapper')).toBeNull();
132
- expect(container.querySelector('.viz-graph-canvas')).toBeNull();
131
+ expect(container.querySelector('.oc-graph-wrapper')).toBeNull();
132
+ expect(container.querySelector('.oc-graph-canvas')).toBeNull();
133
133
 
134
134
  // Calling destroy again should not throw
135
135
  expect(() => graph.destroy()).not.toThrow();
@@ -146,12 +146,12 @@ describe('createGraph', () => {
146
146
  container = makeContainer();
147
147
  const graph = createGraph(container, basicSpec);
148
148
 
149
- const titleBefore = container.querySelector('.viz-title');
149
+ const titleBefore = container.querySelector('.oc-title');
150
150
  expect(titleBefore?.textContent).toBe('Test Graph');
151
151
 
152
152
  graph.update(communitySpec);
153
153
 
154
- const titleAfter = container.querySelector('.viz-title');
154
+ const titleAfter = container.querySelector('.oc-title');
155
155
  expect(titleAfter?.textContent).toBe('Community Graph');
156
156
 
157
157
  graph.destroy();
@@ -161,10 +161,10 @@ describe('createGraph', () => {
161
161
  container = makeContainer();
162
162
  const graph = createGraph(container, communitySpec);
163
163
 
164
- const legend = container.querySelector('.viz-graph-legend');
164
+ const legend = container.querySelector('.oc-graph-legend');
165
165
  expect(legend).not.toBeNull();
166
166
  // Community graph should have visible legend items
167
- const items = container.querySelectorAll('.viz-graph-legend-item');
167
+ const items = container.querySelectorAll('.oc-graph-legend-item');
168
168
  expect(items.length).toBeGreaterThan(0);
169
169
 
170
170
  graph.destroy();
@@ -190,14 +190,14 @@ describe('createGraph', () => {
190
190
  graph.destroy();
191
191
  });
192
192
 
193
- it('applies viz-dark class in dark mode', () => {
193
+ it('applies oc-dark class in dark mode', () => {
194
194
  container = makeContainer();
195
195
  const graph = createGraph(container, basicSpec, { darkMode: 'force' });
196
196
 
197
- expect(container.classList.contains('viz-dark')).toBe(true);
197
+ expect(container.classList.contains('oc-dark')).toBe(true);
198
198
 
199
199
  graph.destroy();
200
- expect(container.classList.contains('viz-dark')).toBe(false);
200
+ expect(container.classList.contains('oc-dark')).toBe(false);
201
201
  });
202
202
 
203
203
  it('onSelectionChange callback fires on selectNode', () => {
@@ -264,35 +264,35 @@ export function createGraph(
264
264
 
265
265
  // Wrapper
266
266
  wrapper = document.createElement('div');
267
- wrapper.className = isDark ? 'viz-graph-wrapper viz-dark' : 'viz-graph-wrapper';
267
+ wrapper.className = isDark ? 'oc-graph-wrapper oc-dark' : 'oc-graph-wrapper';
268
268
  if (isDark) {
269
- container.classList.add('viz-dark');
269
+ container.classList.add('oc-dark');
270
270
  } else {
271
- container.classList.remove('viz-dark');
271
+ container.classList.remove('oc-dark');
272
272
  }
273
273
 
274
274
  // Apply theme colors as CSS custom properties so chrome HTML picks them up.
275
275
  // Without this, consumer-supplied theme.colors.text only affects canvas-drawn
276
- // labels but not the HTML title/subtitle which read from --viz-text.
276
+ // labels but not the HTML title/subtitle which read from --oc-text.
277
277
  const resolvedTheme = compilation.theme;
278
278
  if (resolvedTheme) {
279
279
  const s = wrapper.style;
280
- s.setProperty('--viz-bg', resolvedTheme.colors.background);
281
- s.setProperty('--viz-text', resolvedTheme.colors.text);
282
- s.setProperty('--viz-text-secondary', resolvedTheme.colors.axis ?? resolvedTheme.colors.text);
283
- s.setProperty('--viz-font-family', resolvedTheme.fonts.family);
280
+ s.setProperty('--oc-bg', resolvedTheme.colors.background);
281
+ s.setProperty('--oc-text', resolvedTheme.colors.text);
282
+ s.setProperty('--oc-text-secondary', resolvedTheme.colors.axis ?? resolvedTheme.colors.text);
283
+ s.setProperty('--oc-font-family', resolvedTheme.fonts.family);
284
284
  s.fontFamily = resolvedTheme.fonts.family;
285
285
  }
286
286
 
287
287
  // Chrome (title, subtitle)
288
288
  chromeEl = document.createElement('div');
289
- chromeEl.className = 'viz-graph-chrome';
289
+ chromeEl.className = 'oc-graph-chrome';
290
290
  renderChrome();
291
291
  wrapper.appendChild(chromeEl);
292
292
 
293
293
  // Canvas
294
294
  canvas = document.createElement('canvas');
295
- canvas.className = 'viz-graph-canvas';
295
+ canvas.className = 'oc-graph-canvas';
296
296
  canvas.setAttribute('role', 'img');
297
297
  if (compilation.a11y?.altText) {
298
298
  canvas.setAttribute('aria-label', compilation.a11y.altText);
@@ -302,7 +302,7 @@ export function createGraph(
302
302
  // Legend
303
303
  if (options?.legend !== false) {
304
304
  legendEl = document.createElement('div');
305
- legendEl.className = 'viz-graph-legend';
305
+ legendEl.className = 'oc-graph-legend';
306
306
  renderLegend();
307
307
  wrapper.appendChild(legendEl);
308
308
  }
@@ -321,10 +321,10 @@ export function createGraph(
321
321
  let html = '';
322
322
 
323
323
  if (compilation.chrome.title) {
324
- html += `<h2 class="viz-title">${escapeHtml(compilation.chrome.title.text)}</h2>`;
324
+ html += `<h2 class="oc-title">${escapeHtml(compilation.chrome.title.text)}</h2>`;
325
325
  }
326
326
  if (compilation.chrome.subtitle) {
327
- html += `<p class="viz-subtitle">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;
327
+ html += `<p class="oc-subtitle">${escapeHtml(compilation.chrome.subtitle.text)}</p>`;
328
328
  }
329
329
 
330
330
  chromeEl.innerHTML = html;
@@ -349,8 +349,8 @@ export function createGraph(
349
349
  legendEl.style.display = '';
350
350
  let html = '';
351
351
  for (const entry of entries) {
352
- html += '<div class="viz-graph-legend-item">';
353
- html += `<span class="viz-graph-legend-swatch" style="background:${escapeHtml(entry.color)}"></span>`;
352
+ html += '<div class="oc-graph-legend-item">';
353
+ html += `<span class="oc-graph-legend-swatch" style="background:${escapeHtml(entry.color)}"></span>`;
354
354
  html += `<span>${escapeHtml(entry.label)}</span>`;
355
355
  html += '</div>';
356
356
  }
@@ -588,14 +588,14 @@ export function createGraph(
588
588
  const x = node?.x ?? 0;
589
589
  const y = node?.y ?? 0;
590
590
  simulation?.pinNode(nodeId, x, y);
591
- canvas?.classList.add('viz-graph-canvas--dragging');
591
+ canvas?.classList.add('oc-graph-canvas--dragging');
592
592
  },
593
593
  onNodeDrag(nodeId, x, y) {
594
594
  simulation?.dragNode(nodeId, x, y);
595
595
  },
596
596
  onNodeDragEnd(nodeId) {
597
597
  simulation?.unpinNode(nodeId);
598
- canvas?.classList.remove('viz-graph-canvas--dragging');
598
+ canvas?.classList.remove('oc-graph-canvas--dragging');
599
599
  },
600
600
  onDoubleClick(nodeId) {
601
601
  options?.onNodeDoubleClick?.(nodeDataById(nodeId));
@@ -821,7 +821,7 @@ export function createGraph(
821
821
  legendEl = null;
822
822
  renderer = null;
823
823
 
824
- container.classList.remove('viz-dark');
824
+ container.classList.remove('oc-dark');
825
825
  }
826
826
 
827
827
  // ---------------------------------------------------------------------------