@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.d.ts +53 -5
- package/dist/index.js +897 -168
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -764
- package/package.json +3 -3
- package/src/__tests__/animation.test.ts +358 -0
- package/src/__tests__/edit-events.test.ts +35 -35
- package/src/__tests__/events.test.ts +7 -7
- package/src/__tests__/export.test.ts +1 -1
- package/src/__tests__/mount.test.ts +10 -10
- package/src/__tests__/selection-events.test.ts +869 -0
- package/src/__tests__/svg-renderer.test.ts +67 -67
- package/src/__tests__/table-keyboard.test.ts +18 -18
- package/src/__tests__/table-mount.test.ts +138 -17
- package/src/__tests__/tooltip.test.ts +12 -12
- package/src/animation.ts +75 -0
- package/src/graph/__tests__/graph-mount.test.ts +16 -16
- package/src/graph-mount.ts +18 -18
- package/src/index.ts +3 -1
- package/src/mount.ts +668 -30
- package/src/renderers/table-cells.ts +11 -9
- package/src/svg-renderer.ts +164 -54
- package/src/table-keyboard.ts +5 -5
- package/src/table-mount.ts +34 -11
- package/src/table-renderer.ts +70 -39
- package/src/text-edit-overlay.ts +255 -0
- package/src/tooltip.ts +8 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-vanilla",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@floating-ui/dom": "^1.7.6",
|
|
53
|
-
"@opendata-ai/openchart-core": "6.
|
|
54
|
-
"@opendata-ai/openchart-engine": "6.
|
|
53
|
+
"@opendata-ai/openchart-core": "6.5.0",
|
|
54
|
+
"@opendata-ai/openchart-engine": "6.5.0",
|
|
55
55
|
"d3-force": "^3.0.0",
|
|
56
56
|
"d3-quadtree": "^3.0.1"
|
|
57
57
|
},
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation integration tests for the vanilla renderer.
|
|
3
|
+
*
|
|
4
|
+
* Tests the full pipeline from spec -> compile -> render for animation-related
|
|
5
|
+
* behavior. Verifies that the compiled layout carries animation config and that
|
|
6
|
+
* the SVG output is correctly structured for CSS animations to work.
|
|
7
|
+
*
|
|
8
|
+
* Note: CSS animation keyframes and class-based triggers are defined in the
|
|
9
|
+
* CSS partials under packages/core/src/styles/. These tests verify the DOM
|
|
10
|
+
* structure that CSS hooks into, not the visual animation itself (which
|
|
11
|
+
* requires a real browser).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ChartSpec } from '@opendata-ai/openchart-engine';
|
|
15
|
+
import { compileChart } from '@opendata-ai/openchart-engine';
|
|
16
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
17
|
+
import { createContainer } from '../__test-fixtures__/dom';
|
|
18
|
+
import { barSpec, columnSpec, lineSpec } from '../__test-fixtures__/specs';
|
|
19
|
+
import { createChart } from '../mount';
|
|
20
|
+
import { renderChartSVG } from '../svg-renderer';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
function compileAndRender(spec: ChartSpec, width = 600, height = 400) {
|
|
27
|
+
const container = createContainer(width, height);
|
|
28
|
+
const layout = compileChart(spec, { width, height });
|
|
29
|
+
const svg = renderChartSVG(layout, container);
|
|
30
|
+
return { svg, container, layout };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Cleanup
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
document.body.innerHTML = '';
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Layout carries animation config through to render
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe('animation in compiled layout', () => {
|
|
46
|
+
it('layout has animation config when spec enables animation', () => {
|
|
47
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
48
|
+
const layout = compileChart(spec, { width: 600, height: 400 });
|
|
49
|
+
expect(layout.animation).toBeDefined();
|
|
50
|
+
expect(layout.animation!.enabled).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('layout has no animation config when spec omits animation', () => {
|
|
54
|
+
const layout = compileChart(barSpec, { width: 600, height: 400 });
|
|
55
|
+
expect(layout.animation).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// SVG mark structure supports CSS animation selectors
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
describe('SVG structure for animation CSS hooks', () => {
|
|
64
|
+
it('bar chart renders rect elements inside oc-mark groups', () => {
|
|
65
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
66
|
+
const { svg } = compileAndRender(spec);
|
|
67
|
+
|
|
68
|
+
// CSS animations target .oc-mark-rect rect and .oc-mark-bar rect
|
|
69
|
+
const rects = svg.querySelectorAll('.oc-marks rect');
|
|
70
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('column chart renders rect elements for vertical bars', () => {
|
|
74
|
+
const spec = { ...columnSpec, animation: true } as ChartSpec;
|
|
75
|
+
const { svg } = compileAndRender(spec);
|
|
76
|
+
|
|
77
|
+
const rects = svg.querySelectorAll('.oc-marks rect');
|
|
78
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('line chart renders path elements inside oc-mark-line groups', () => {
|
|
82
|
+
const spec = { ...lineSpec, animation: true } as ChartSpec;
|
|
83
|
+
const { svg } = compileAndRender(spec);
|
|
84
|
+
|
|
85
|
+
const paths = svg.querySelectorAll('.oc-mark-line path');
|
|
86
|
+
expect(paths.length).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('mark groups have data-mark-id attributes for targeting', () => {
|
|
90
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
91
|
+
const { svg } = compileAndRender(spec);
|
|
92
|
+
|
|
93
|
+
const markGroups = svg.querySelectorAll('[data-mark-id]');
|
|
94
|
+
expect(markGroups.length).toBeGreaterThan(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('SVG root has oc-chart class (oc-animate is added by mount, not renderer)', () => {
|
|
98
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
99
|
+
const { svg } = compileAndRender(spec);
|
|
100
|
+
expect(svg.getAttribute('class')).toContain('oc-chart');
|
|
101
|
+
// oc-animate is added by mount.ts on first render only, not by the renderer
|
|
102
|
+
expect(svg.getAttribute('class')).not.toContain('oc-animate');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('SVG root does not have oc-animate class when animation is disabled', () => {
|
|
106
|
+
const { svg } = compileAndRender(barSpec);
|
|
107
|
+
expect(svg.getAttribute('class')).toContain('oc-chart');
|
|
108
|
+
expect(svg.getAttribute('class')).not.toContain('oc-animate');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Animation CSS custom properties on SVG root
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe('animation CSS custom properties', () => {
|
|
117
|
+
it('sets --oc-animation-duration on SVG root', () => {
|
|
118
|
+
const spec = {
|
|
119
|
+
...barSpec,
|
|
120
|
+
animation: { enter: { duration: 800 } },
|
|
121
|
+
} as ChartSpec;
|
|
122
|
+
const { svg } = compileAndRender(spec);
|
|
123
|
+
expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('800ms');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('sets --oc-animation-stagger on SVG root', () => {
|
|
127
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
128
|
+
const { svg } = compileAndRender(spec);
|
|
129
|
+
const stagger = svg.style.getPropertyValue('--oc-animation-stagger');
|
|
130
|
+
expect(stagger).toMatch(/^\d+ms$/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('sets --oc-annotation-delay on SVG root', () => {
|
|
134
|
+
const spec = {
|
|
135
|
+
...barSpec,
|
|
136
|
+
animation: { enter: true, annotationDelay: 500 },
|
|
137
|
+
} as ChartSpec;
|
|
138
|
+
const { svg } = compileAndRender(spec);
|
|
139
|
+
expect(svg.style.getPropertyValue('--oc-annotation-delay')).toBe('500ms');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('does not set animation custom properties when animation is disabled', () => {
|
|
143
|
+
const { svg } = compileAndRender(barSpec);
|
|
144
|
+
expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('');
|
|
145
|
+
expect(svg.style.getPropertyValue('--oc-animation-stagger')).toBe('');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// --oc-mark-index stamped on mark elements
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
describe('mark animation index attributes', () => {
|
|
154
|
+
it('stamps --oc-mark-index on rect mark groups', () => {
|
|
155
|
+
const spec = { ...columnSpec, animation: true } as ChartSpec;
|
|
156
|
+
const { svg } = compileAndRender(spec);
|
|
157
|
+
|
|
158
|
+
const markGroups = svg.querySelectorAll('.oc-mark-rect');
|
|
159
|
+
expect(markGroups.length).toBeGreaterThan(0);
|
|
160
|
+
|
|
161
|
+
for (const group of markGroups) {
|
|
162
|
+
const el = group as SVGElement & ElementCSSInlineStyle;
|
|
163
|
+
const markIndex = el.style.getPropertyValue('--oc-mark-index');
|
|
164
|
+
expect(markIndex).not.toBe('');
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('stamps data-animation-index attribute on mark groups', () => {
|
|
169
|
+
const spec = { ...columnSpec, animation: true } as ChartSpec;
|
|
170
|
+
const { svg } = compileAndRender(spec);
|
|
171
|
+
|
|
172
|
+
const markGroups = svg.querySelectorAll('[data-animation-index]');
|
|
173
|
+
expect(markGroups.length).toBeGreaterThan(0);
|
|
174
|
+
|
|
175
|
+
// Each should have a numeric index
|
|
176
|
+
for (const group of markGroups) {
|
|
177
|
+
const idx = group.getAttribute('data-animation-index');
|
|
178
|
+
expect(idx).toMatch(/^\d+$/);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('does not stamp animation attributes when animation is disabled', () => {
|
|
183
|
+
const { svg } = compileAndRender(columnSpec);
|
|
184
|
+
|
|
185
|
+
const withAnimIdx = svg.querySelectorAll('[data-animation-index]');
|
|
186
|
+
expect(withAnimIdx.length).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('stamps --oc-mark-index on line mark groups', () => {
|
|
190
|
+
const spec = { ...lineSpec, animation: true } as ChartSpec;
|
|
191
|
+
const { svg } = compileAndRender(spec);
|
|
192
|
+
|
|
193
|
+
const lineGroups = svg.querySelectorAll('.oc-mark-line');
|
|
194
|
+
expect(lineGroups.length).toBeGreaterThan(0);
|
|
195
|
+
|
|
196
|
+
for (const group of lineGroups) {
|
|
197
|
+
const el = group as SVGElement & ElementCSSInlineStyle;
|
|
198
|
+
const markIndex = el.style.getPropertyValue('--oc-mark-index');
|
|
199
|
+
expect(markIndex).not.toBe('');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// data-orient attribute for horizontal bars
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
describe('data-orient for horizontal bars', () => {
|
|
209
|
+
it('sets data-orient=horizontal on mark groups for horizontal bar charts', () => {
|
|
210
|
+
// barSpec has x=quantitative, y=nominal which is a horizontal bar chart
|
|
211
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
212
|
+
const { svg } = compileAndRender(spec);
|
|
213
|
+
|
|
214
|
+
const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
|
|
215
|
+
expect(horizontalGroups.length).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('does not set data-orient=horizontal on vertical column charts', () => {
|
|
219
|
+
// columnSpec has x=nominal, y=quantitative which is a vertical column chart
|
|
220
|
+
const spec = { ...columnSpec, animation: true } as ChartSpec;
|
|
221
|
+
const { svg } = compileAndRender(spec);
|
|
222
|
+
|
|
223
|
+
const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
|
|
224
|
+
expect(horizontalGroups.length).toBe(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Value-based stagger ordering assigns animationIndex on marks
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
describe('animationIndex for value-based stagger', () => {
|
|
233
|
+
it('assigns animationIndex to rect marks when stagger order is value', () => {
|
|
234
|
+
const spec = {
|
|
235
|
+
...columnSpec,
|
|
236
|
+
animation: {
|
|
237
|
+
enter: { stagger: { order: 'value' as const } },
|
|
238
|
+
},
|
|
239
|
+
} as ChartSpec;
|
|
240
|
+
const layout = compileChart(spec, { width: 600, height: 400 });
|
|
241
|
+
|
|
242
|
+
const rectMarks = layout.marks.filter((m) => m.type === 'rect');
|
|
243
|
+
expect(rectMarks.length).toBe(3);
|
|
244
|
+
|
|
245
|
+
// Each rect mark should have animationIndex
|
|
246
|
+
for (const mark of rectMarks) {
|
|
247
|
+
const idx = (mark as unknown as { animationIndex?: number }).animationIndex;
|
|
248
|
+
expect(idx).toBeDefined();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('orders marks by their primary quantitative value', () => {
|
|
253
|
+
// columnSpec has revenues: Q1=100, Q2=200, Q3=150
|
|
254
|
+
// Value ordering should sort by bar height (revenue)
|
|
255
|
+
const spec = {
|
|
256
|
+
...columnSpec,
|
|
257
|
+
animation: {
|
|
258
|
+
enter: { stagger: { order: 'value' as const } },
|
|
259
|
+
},
|
|
260
|
+
} as ChartSpec;
|
|
261
|
+
const layout = compileChart(spec, { width: 600, height: 400 });
|
|
262
|
+
|
|
263
|
+
const rectMarks = layout.marks.filter((m) => m.type === 'rect');
|
|
264
|
+
const indexed = rectMarks.map((m) => ({
|
|
265
|
+
index: (m as unknown as { animationIndex: number }).animationIndex,
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
// All indices should be unique
|
|
269
|
+
const indices = indexed.map((m) => m.index);
|
|
270
|
+
const uniqueIndices = new Set(indices);
|
|
271
|
+
expect(uniqueIndices.size).toBe(rectMarks.length);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// createChart mount lifecycle with animation
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
describe('createChart animation lifecycle', () => {
|
|
280
|
+
it('produces valid SVG on mount with animation enabled', () => {
|
|
281
|
+
const container = createContainer();
|
|
282
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
283
|
+
const chart = createChart(container, spec);
|
|
284
|
+
|
|
285
|
+
const svg = container.querySelector('svg');
|
|
286
|
+
expect(svg).not.toBeNull();
|
|
287
|
+
expect(svg?.getAttribute('class')).toContain('oc-chart');
|
|
288
|
+
expect(svg?.getAttribute('class')).toContain('oc-animate');
|
|
289
|
+
|
|
290
|
+
// Marks are rendered
|
|
291
|
+
const rects = container.querySelectorAll('.oc-marks rect');
|
|
292
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
293
|
+
|
|
294
|
+
chart.destroy();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('update re-renders chart correctly with animation', () => {
|
|
298
|
+
const container = createContainer();
|
|
299
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
300
|
+
const chart = createChart(container, spec);
|
|
301
|
+
|
|
302
|
+
// Verify initial render
|
|
303
|
+
let rects = container.querySelectorAll('.oc-marks rect');
|
|
304
|
+
const initialCount = rects.length;
|
|
305
|
+
expect(initialCount).toBeGreaterThan(0);
|
|
306
|
+
|
|
307
|
+
// Update with different data but same animation setting
|
|
308
|
+
const updatedSpec = {
|
|
309
|
+
...barSpec,
|
|
310
|
+
data: [
|
|
311
|
+
{ name: 'X', value: 50 },
|
|
312
|
+
{ name: 'Y', value: 70 },
|
|
313
|
+
],
|
|
314
|
+
animation: true,
|
|
315
|
+
} as ChartSpec;
|
|
316
|
+
chart.update(updatedSpec);
|
|
317
|
+
|
|
318
|
+
// SVG is re-rendered
|
|
319
|
+
const svg = container.querySelector('svg');
|
|
320
|
+
expect(svg).not.toBeNull();
|
|
321
|
+
|
|
322
|
+
// Marks reflect updated data
|
|
323
|
+
rects = container.querySelectorAll('.oc-marks rect');
|
|
324
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
325
|
+
|
|
326
|
+
chart.destroy();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('works correctly when animation is toggled off on update', () => {
|
|
330
|
+
const container = createContainer();
|
|
331
|
+
const specWithAnim = { ...barSpec, animation: true } as ChartSpec;
|
|
332
|
+
const chart = createChart(container, specWithAnim);
|
|
333
|
+
|
|
334
|
+
// Update with animation disabled
|
|
335
|
+
const specNoAnim = { ...barSpec, animation: false } as ChartSpec;
|
|
336
|
+
chart.update(specNoAnim);
|
|
337
|
+
|
|
338
|
+
// Chart should still render correctly
|
|
339
|
+
const svg = container.querySelector('svg');
|
|
340
|
+
expect(svg).not.toBeNull();
|
|
341
|
+
const rects = container.querySelectorAll('.oc-marks rect');
|
|
342
|
+
expect(rects.length).toBeGreaterThan(0);
|
|
343
|
+
|
|
344
|
+
chart.destroy();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('destroy cleans up the container', () => {
|
|
348
|
+
const container = createContainer();
|
|
349
|
+
const spec = { ...barSpec, animation: true } as ChartSpec;
|
|
350
|
+
const chart = createChart(container, spec);
|
|
351
|
+
|
|
352
|
+
expect(container.querySelector('svg')).not.toBeNull();
|
|
353
|
+
|
|
354
|
+
chart.destroy();
|
|
355
|
+
|
|
356
|
+
expect(container.querySelector('svg')).toBeNull();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
@@ -105,7 +105,7 @@ describe('edit events', () => {
|
|
|
105
105
|
const onEdit = vi.fn();
|
|
106
106
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
107
107
|
|
|
108
|
-
const annotation = container.querySelector('.
|
|
108
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
109
109
|
if (!annotation) {
|
|
110
110
|
chart.destroy();
|
|
111
111
|
return;
|
|
@@ -119,7 +119,7 @@ describe('edit events', () => {
|
|
|
119
119
|
const onEdit = vi.fn();
|
|
120
120
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
121
121
|
|
|
122
|
-
const annotation = container.querySelector('.
|
|
122
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
123
123
|
if (!annotation) {
|
|
124
124
|
chart.destroy();
|
|
125
125
|
return;
|
|
@@ -143,7 +143,7 @@ describe('edit events', () => {
|
|
|
143
143
|
const onEdit = vi.fn();
|
|
144
144
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
145
145
|
|
|
146
|
-
const annotation = container.querySelector('.
|
|
146
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
147
147
|
if (!annotation) {
|
|
148
148
|
chart.destroy();
|
|
149
149
|
return;
|
|
@@ -161,7 +161,7 @@ describe('edit events', () => {
|
|
|
161
161
|
const onEdit = vi.fn();
|
|
162
162
|
const chart = createChart(container, textAnnotatedSpec, { onAnnotationClick, onEdit });
|
|
163
163
|
|
|
164
|
-
const annotation = container.querySelector('.
|
|
164
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
165
165
|
if (!annotation) {
|
|
166
166
|
chart.destroy();
|
|
167
167
|
return;
|
|
@@ -184,7 +184,7 @@ describe('edit events', () => {
|
|
|
184
184
|
const onEdit = vi.fn();
|
|
185
185
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
186
186
|
|
|
187
|
-
const annotation = container.querySelector('.
|
|
187
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
188
188
|
if (!annotation) {
|
|
189
189
|
chart.destroy();
|
|
190
190
|
return;
|
|
@@ -227,19 +227,19 @@ describe('edit events', () => {
|
|
|
227
227
|
const chart = createChart(container, fullEditSpec, {});
|
|
228
228
|
|
|
229
229
|
// Chrome elements should not have grab cursor
|
|
230
|
-
const chromeTexts = container.querySelectorAll('.
|
|
230
|
+
const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
|
|
231
231
|
for (const el of chromeTexts) {
|
|
232
232
|
expect((el as HTMLElement).style.cursor).not.toBe('grab');
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
// Legend should not have grab cursor
|
|
236
|
-
const legendG = container.querySelector('.
|
|
236
|
+
const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
|
|
237
237
|
if (legendG) {
|
|
238
238
|
expect(legendG.style.cursor).not.toBe('grab');
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
// Series labels should not have grab cursor
|
|
242
|
-
const seriesLabels = container.querySelectorAll('.
|
|
242
|
+
const seriesLabels = container.querySelectorAll('.oc-mark-label');
|
|
243
243
|
for (const el of seriesLabels) {
|
|
244
244
|
expect((el as HTMLElement).style.cursor).not.toBe('grab');
|
|
245
245
|
}
|
|
@@ -252,19 +252,19 @@ describe('edit events', () => {
|
|
|
252
252
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
253
253
|
|
|
254
254
|
// Chrome elements should have grab cursor
|
|
255
|
-
const chromeTexts = container.querySelectorAll('.
|
|
255
|
+
const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
|
|
256
256
|
for (const el of chromeTexts) {
|
|
257
257
|
expect((el as HTMLElement).style.cursor).toBe('grab');
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// Legend should have grab cursor (if present)
|
|
261
|
-
const legendG = container.querySelector('.
|
|
261
|
+
const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
|
|
262
262
|
if (legendG) {
|
|
263
263
|
expect(legendG.style.cursor).toBe('grab');
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
// Series labels should have grab cursor (if any rendered with data-series)
|
|
267
|
-
const seriesLabels = container.querySelectorAll('.
|
|
267
|
+
const seriesLabels = container.querySelectorAll('.oc-mark-label[data-series]');
|
|
268
268
|
for (const el of seriesLabels) {
|
|
269
269
|
expect((el as HTMLElement).style.cursor).toBe('grab');
|
|
270
270
|
}
|
|
@@ -277,7 +277,7 @@ describe('edit events', () => {
|
|
|
277
277
|
const onEdit = vi.fn();
|
|
278
278
|
const chart = createChart(container, textAnnotatedSpec, { onAnnotationEdit, onEdit });
|
|
279
279
|
|
|
280
|
-
const annotation = container.querySelector('.
|
|
280
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
281
281
|
if (!annotation) {
|
|
282
282
|
chart.destroy();
|
|
283
283
|
return;
|
|
@@ -311,7 +311,7 @@ describe('edit events', () => {
|
|
|
311
311
|
const onEdit = vi.fn();
|
|
312
312
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
313
313
|
|
|
314
|
-
const annotation = container.querySelector('.
|
|
314
|
+
const annotation = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
315
315
|
if (!annotation) {
|
|
316
316
|
chart.destroy();
|
|
317
317
|
return;
|
|
@@ -336,7 +336,7 @@ describe('edit events', () => {
|
|
|
336
336
|
const chart = createChart(container, rangeAnnotatedSpec, { onEdit });
|
|
337
337
|
|
|
338
338
|
const rangeLabel = container.querySelector(
|
|
339
|
-
'.
|
|
339
|
+
'.oc-annotation-range .oc-annotation-label',
|
|
340
340
|
) as SVGTextElement | null;
|
|
341
341
|
if (!rangeLabel) {
|
|
342
342
|
// Range may not render a visible label in test env
|
|
@@ -353,7 +353,7 @@ describe('edit events', () => {
|
|
|
353
353
|
const chart = createChart(container, rangeAnnotatedSpec, { onEdit });
|
|
354
354
|
|
|
355
355
|
const rangeLabel = container.querySelector(
|
|
356
|
-
'.
|
|
356
|
+
'.oc-annotation-range .oc-annotation-label',
|
|
357
357
|
) as SVGTextElement | null;
|
|
358
358
|
if (!rangeLabel) {
|
|
359
359
|
chart.destroy();
|
|
@@ -380,7 +380,7 @@ describe('edit events', () => {
|
|
|
380
380
|
const chart = createChart(container, reflineAnnotatedSpec, { onEdit });
|
|
381
381
|
|
|
382
382
|
const reflineLabel = container.querySelector(
|
|
383
|
-
'.
|
|
383
|
+
'.oc-annotation-refline .oc-annotation-label',
|
|
384
384
|
) as SVGTextElement | null;
|
|
385
385
|
if (!reflineLabel) {
|
|
386
386
|
chart.destroy();
|
|
@@ -396,7 +396,7 @@ describe('edit events', () => {
|
|
|
396
396
|
const chart = createChart(container, reflineAnnotatedSpec, { onEdit });
|
|
397
397
|
|
|
398
398
|
const reflineLabel = container.querySelector(
|
|
399
|
-
'.
|
|
399
|
+
'.oc-annotation-refline .oc-annotation-label',
|
|
400
400
|
) as SVGTextElement | null;
|
|
401
401
|
if (!reflineLabel) {
|
|
402
402
|
chart.destroy();
|
|
@@ -427,7 +427,7 @@ describe('edit events', () => {
|
|
|
427
427
|
const onEdit = vi.fn();
|
|
428
428
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
429
429
|
|
|
430
|
-
const chromeTexts = container.querySelectorAll('.
|
|
430
|
+
const chromeTexts = container.querySelectorAll('.oc-chrome text[data-chrome-key]');
|
|
431
431
|
expect(chromeTexts.length).toBeGreaterThan(0);
|
|
432
432
|
|
|
433
433
|
for (const el of chromeTexts) {
|
|
@@ -442,7 +442,7 @@ describe('edit events', () => {
|
|
|
442
442
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
443
443
|
|
|
444
444
|
const titleEl = container.querySelector(
|
|
445
|
-
'.
|
|
445
|
+
'.oc-chrome text[data-chrome-key="title"]',
|
|
446
446
|
) as SVGTextElement | null;
|
|
447
447
|
if (!titleEl) {
|
|
448
448
|
chart.destroy();
|
|
@@ -474,7 +474,7 @@ describe('edit events', () => {
|
|
|
474
474
|
const onEdit = vi.fn();
|
|
475
475
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
476
476
|
|
|
477
|
-
const legendG = container.querySelector('.
|
|
477
|
+
const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
|
|
478
478
|
if (!legendG) {
|
|
479
479
|
chart.destroy();
|
|
480
480
|
return;
|
|
@@ -488,7 +488,7 @@ describe('edit events', () => {
|
|
|
488
488
|
const onEdit = vi.fn();
|
|
489
489
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
490
490
|
|
|
491
|
-
const legendG = container.querySelector('.
|
|
491
|
+
const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
|
|
492
492
|
if (!legendG) {
|
|
493
493
|
chart.destroy();
|
|
494
494
|
return;
|
|
@@ -512,7 +512,7 @@ describe('edit events', () => {
|
|
|
512
512
|
const onEdit = vi.fn();
|
|
513
513
|
const chart = createChart(container, fullEditSpec, { onLegendToggle, onEdit });
|
|
514
514
|
|
|
515
|
-
const legendG = container.querySelector('.
|
|
515
|
+
const legendG = container.querySelector('.oc-legend') as SVGGElement | null;
|
|
516
516
|
if (!legendG) {
|
|
517
517
|
chart.destroy();
|
|
518
518
|
return;
|
|
@@ -540,16 +540,16 @@ describe('edit events', () => {
|
|
|
540
540
|
const onEdit = vi.fn();
|
|
541
541
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
542
542
|
|
|
543
|
-
const annotationG = container.querySelector('.
|
|
543
|
+
const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
544
544
|
if (!annotationG) {
|
|
545
545
|
chart.destroy();
|
|
546
546
|
return;
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
// Check that connector handles exist within the annotation group
|
|
550
|
-
const handles = annotationG.querySelectorAll('.
|
|
550
|
+
const handles = annotationG.querySelectorAll('.oc-connector-handle');
|
|
551
551
|
// There should be 2 handles (from and to) if a connector is present
|
|
552
|
-
const connector = annotationG.querySelector('.
|
|
552
|
+
const connector = annotationG.querySelector('.oc-annotation-connector');
|
|
553
553
|
if (connector) {
|
|
554
554
|
expect(handles.length).toBe(2);
|
|
555
555
|
// Verify they have the correct data-endpoint attributes
|
|
@@ -565,13 +565,13 @@ describe('edit events', () => {
|
|
|
565
565
|
const onEdit = vi.fn();
|
|
566
566
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
567
567
|
|
|
568
|
-
const annotationG = container.querySelector('.
|
|
568
|
+
const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
569
569
|
if (!annotationG) {
|
|
570
570
|
chart.destroy();
|
|
571
571
|
return;
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
-
const handles = annotationG.querySelectorAll('.
|
|
574
|
+
const handles = annotationG.querySelectorAll('.oc-connector-handle');
|
|
575
575
|
if (handles.length === 0) {
|
|
576
576
|
chart.destroy();
|
|
577
577
|
return;
|
|
@@ -597,13 +597,13 @@ describe('edit events', () => {
|
|
|
597
597
|
const onEdit = vi.fn();
|
|
598
598
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
599
599
|
|
|
600
|
-
const annotationG = container.querySelector('.
|
|
600
|
+
const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
601
601
|
if (!annotationG) {
|
|
602
602
|
chart.destroy();
|
|
603
603
|
return;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
const handles = annotationG.querySelectorAll('.
|
|
606
|
+
const handles = annotationG.querySelectorAll('.oc-connector-handle');
|
|
607
607
|
if (handles.length === 0) {
|
|
608
608
|
chart.destroy();
|
|
609
609
|
return;
|
|
@@ -625,7 +625,7 @@ describe('edit events', () => {
|
|
|
625
625
|
const onEdit = vi.fn();
|
|
626
626
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
627
627
|
|
|
628
|
-
const annotationG = container.querySelector('.
|
|
628
|
+
const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
629
629
|
if (!annotationG) {
|
|
630
630
|
chart.destroy();
|
|
631
631
|
return;
|
|
@@ -633,7 +633,7 @@ describe('edit events', () => {
|
|
|
633
633
|
|
|
634
634
|
// Find the "from" handle
|
|
635
635
|
const fromHandle = annotationG.querySelector(
|
|
636
|
-
'.
|
|
636
|
+
'.oc-connector-handle[data-endpoint="from"]',
|
|
637
637
|
) as SVGCircleElement | null;
|
|
638
638
|
|
|
639
639
|
if (!fromHandle) {
|
|
@@ -661,14 +661,14 @@ describe('edit events', () => {
|
|
|
661
661
|
const onEdit = vi.fn();
|
|
662
662
|
const chart = createChart(container, textAnnotatedSpec, { onEdit });
|
|
663
663
|
|
|
664
|
-
const annotationG = container.querySelector('.
|
|
664
|
+
const annotationG = container.querySelector('.oc-annotation-text') as SVGGElement | null;
|
|
665
665
|
if (!annotationG) {
|
|
666
666
|
chart.destroy();
|
|
667
667
|
return;
|
|
668
668
|
}
|
|
669
669
|
|
|
670
670
|
const toHandle = annotationG.querySelector(
|
|
671
|
-
'.
|
|
671
|
+
'.oc-connector-handle[data-endpoint="to"]',
|
|
672
672
|
) as SVGCircleElement | null;
|
|
673
673
|
|
|
674
674
|
if (!toHandle) {
|
|
@@ -697,7 +697,7 @@ describe('edit events', () => {
|
|
|
697
697
|
const onEdit = vi.fn();
|
|
698
698
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
699
699
|
|
|
700
|
-
const seriesLabels = container.querySelectorAll('.
|
|
700
|
+
const seriesLabels = container.querySelectorAll('.oc-mark-label[data-series]');
|
|
701
701
|
if (seriesLabels.length === 0) {
|
|
702
702
|
// Labels may not render in the test env depending on layout
|
|
703
703
|
chart.destroy();
|
|
@@ -716,7 +716,7 @@ describe('edit events', () => {
|
|
|
716
716
|
const chart = createChart(container, fullEditSpec, { onEdit });
|
|
717
717
|
|
|
718
718
|
const seriesLabel = container.querySelector(
|
|
719
|
-
'.
|
|
719
|
+
'.oc-mark-label[data-series]',
|
|
720
720
|
) as SVGTextElement | null;
|
|
721
721
|
if (!seriesLabel) {
|
|
722
722
|
chart.destroy();
|