@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.
- package/dist/index.d.ts +9 -2
- package/dist/index.js +327 -174
- 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 +14 -14
- 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/mount.ts +71 -37
- package/src/renderers/table-cells.ts +11 -9
- package/src/svg-renderer.ts +161 -54
- package/src/table-keyboard.ts +5 -5
- package/src/table-mount.ts +34 -11
- package/src/table-renderer.ts +70 -39
- package/src/tooltip.ts +8 -8
package/src/mount.ts
CHANGED
|
@@ -29,6 +29,7 @@ import type {
|
|
|
29
29
|
} from '@opendata-ai/openchart-core';
|
|
30
30
|
import { elementRef, isLayerSpec } from '@opendata-ai/openchart-core';
|
|
31
31
|
import { compileChart, compileLayer } from '@opendata-ai/openchart-engine';
|
|
32
|
+
import { cancelAnimations, setupAnimationCleanup } from './animation';
|
|
32
33
|
import {
|
|
33
34
|
exportCSV,
|
|
34
35
|
exportJPG,
|
|
@@ -477,7 +478,7 @@ function wireChartEvents(
|
|
|
477
478
|
|
|
478
479
|
// Wire annotation click events
|
|
479
480
|
if (handlers.onAnnotationClick) {
|
|
480
|
-
const annotationElements = svg.querySelectorAll('.
|
|
481
|
+
const annotationElements = svg.querySelectorAll('.oc-annotation');
|
|
481
482
|
|
|
482
483
|
for (let i = 0; i < annotationElements.length; i++) {
|
|
483
484
|
const el = annotationElements[i];
|
|
@@ -713,7 +714,7 @@ function wireAnnotationDrag(
|
|
|
713
714
|
onEdit: ((edit: ElementEdit) => void) | undefined,
|
|
714
715
|
setDragging: (dragging: boolean) => void,
|
|
715
716
|
): () => void {
|
|
716
|
-
const annotationElements = svg.querySelectorAll('.
|
|
717
|
+
const annotationElements = svg.querySelectorAll('.oc-annotation-text');
|
|
717
718
|
const cleanups: Array<() => void> = [];
|
|
718
719
|
|
|
719
720
|
for (const el of annotationElements) {
|
|
@@ -731,13 +732,13 @@ function wireAnnotationDrag(
|
|
|
731
732
|
annotationG.style.cursor = 'grab';
|
|
732
733
|
|
|
733
734
|
// Stash connector info for real-time updates during drag
|
|
734
|
-
const connectorLine = annotationG.querySelector('line.
|
|
735
|
+
const connectorLine = annotationG.querySelector('line.oc-annotation-connector');
|
|
735
736
|
const origX2 = connectorLine ? Number(connectorLine.getAttribute('x2')) : 0;
|
|
736
737
|
const origY2 = connectorLine ? Number(connectorLine.getAttribute('y2')) : 0;
|
|
737
738
|
|
|
738
739
|
// For curved connectors, stash path/polygon elements to hide during drag
|
|
739
|
-
const curvedPath = annotationG.querySelector('path.
|
|
740
|
-
const arrowhead = annotationG.querySelector('polygon.
|
|
740
|
+
const curvedPath = annotationG.querySelector('path.oc-annotation-connector');
|
|
741
|
+
const arrowhead = annotationG.querySelector('polygon.oc-annotation-connector');
|
|
741
742
|
const hasCurvedConnector = curvedPath !== null;
|
|
742
743
|
|
|
743
744
|
const origDx = textAnnotation.offset?.dx ?? 0;
|
|
@@ -824,7 +825,7 @@ function wireConnectorEndpointDrag(
|
|
|
824
825
|
): () => void {
|
|
825
826
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
826
827
|
const cleanups: Array<() => void> = [];
|
|
827
|
-
const annotationGroups = svg.querySelectorAll('.
|
|
828
|
+
const annotationGroups = svg.querySelectorAll('.oc-annotation-text');
|
|
828
829
|
|
|
829
830
|
for (const el of annotationGroups) {
|
|
830
831
|
const annotationG = el as SVGGElement;
|
|
@@ -838,8 +839,8 @@ function wireConnectorEndpointDrag(
|
|
|
838
839
|
const textAnnotation = specAnnotation as TextAnnotation;
|
|
839
840
|
|
|
840
841
|
// Find connector line or curved connector to determine endpoints
|
|
841
|
-
const connectorLine = annotationG.querySelector('line.
|
|
842
|
-
const curvedPath = annotationG.querySelector('path.
|
|
842
|
+
const connectorLine = annotationG.querySelector('line.oc-annotation-connector');
|
|
843
|
+
const curvedPath = annotationG.querySelector('path.oc-annotation-connector');
|
|
843
844
|
if (!connectorLine && !curvedPath) continue;
|
|
844
845
|
|
|
845
846
|
// Determine connector endpoint positions from the connector element
|
|
@@ -857,7 +858,7 @@ function wireConnectorEndpointDrag(
|
|
|
857
858
|
fromX = mMatch ? Number(mMatch[1]) : 0;
|
|
858
859
|
fromY = mMatch ? Number(mMatch[2]) : 0;
|
|
859
860
|
// For curved connectors, the arrow polygon has the target
|
|
860
|
-
const arrowhead = annotationG.querySelector('polygon.
|
|
861
|
+
const arrowhead = annotationG.querySelector('polygon.oc-annotation-connector');
|
|
861
862
|
const points = arrowhead?.getAttribute('points') ?? '';
|
|
862
863
|
const firstPoint = points.split(' ')[0] ?? '0,0';
|
|
863
864
|
const [px, py] = firstPoint.split(',');
|
|
@@ -878,7 +879,7 @@ function wireConnectorEndpointDrag(
|
|
|
878
879
|
if (!Number.isFinite(ep.cx) || !Number.isFinite(ep.cy)) continue;
|
|
879
880
|
|
|
880
881
|
const handleEl = document.createElementNS(SVG_NS, 'circle') as SVGCircleElement;
|
|
881
|
-
handleEl.setAttribute('class', '
|
|
882
|
+
handleEl.setAttribute('class', 'oc-connector-handle');
|
|
882
883
|
handleEl.setAttribute('data-endpoint', ep.name);
|
|
883
884
|
handleEl.setAttribute('cx', String(ep.cx));
|
|
884
885
|
handleEl.setAttribute('cy', String(ep.cy));
|
|
@@ -1002,15 +1003,15 @@ function wireAnnotationLabelDrag(
|
|
|
1002
1003
|
|
|
1003
1004
|
// Target range and refline annotation labels
|
|
1004
1005
|
const selectors = [
|
|
1005
|
-
'.
|
|
1006
|
-
'.
|
|
1006
|
+
'.oc-annotation-range .oc-annotation-label',
|
|
1007
|
+
'.oc-annotation-refline .oc-annotation-label',
|
|
1007
1008
|
];
|
|
1008
1009
|
|
|
1009
1010
|
for (const selector of selectors) {
|
|
1010
1011
|
const labels = svg.querySelectorAll(selector);
|
|
1011
1012
|
|
|
1012
1013
|
for (const label of labels) {
|
|
1013
|
-
const annotationG = label.closest('.
|
|
1014
|
+
const annotationG = label.closest('.oc-annotation') as SVGGElement | null;
|
|
1014
1015
|
if (!annotationG) continue;
|
|
1015
1016
|
|
|
1016
1017
|
const indexStr = annotationG.getAttribute('data-annotation-index');
|
|
@@ -1085,7 +1086,7 @@ function wireChromeDrag(
|
|
|
1085
1086
|
onEdit: (edit: ElementEdit) => void,
|
|
1086
1087
|
setDragging: (dragging: boolean) => void,
|
|
1087
1088
|
): () => void {
|
|
1088
|
-
const chromeTexts = svg.querySelectorAll('.
|
|
1089
|
+
const chromeTexts = svg.querySelectorAll('.oc-chrome text[data-chrome-key]');
|
|
1089
1090
|
const cleanups: Array<() => void> = [];
|
|
1090
1091
|
|
|
1091
1092
|
// Read existing chrome offsets from the spec
|
|
@@ -1153,7 +1154,7 @@ function wireLegendDrag(
|
|
|
1153
1154
|
onEdit: (edit: ElementEdit) => void,
|
|
1154
1155
|
setDragging: (dragging: boolean) => void,
|
|
1155
1156
|
): () => void {
|
|
1156
|
-
const legendG = svg.querySelector('.
|
|
1157
|
+
const legendG = svg.querySelector('.oc-legend') as SVGGElement | null;
|
|
1157
1158
|
if (!legendG) return () => {};
|
|
1158
1159
|
|
|
1159
1160
|
const cleanups: Array<() => void> = [];
|
|
@@ -1197,7 +1198,7 @@ function wireLegendDrag(
|
|
|
1197
1198
|
// ---------------------------------------------------------------------------
|
|
1198
1199
|
|
|
1199
1200
|
/**
|
|
1200
|
-
* Wire drag on series label elements (.
|
|
1201
|
+
* Wire drag on series label elements (.oc-mark-label[data-series]).
|
|
1201
1202
|
* On drag end, fires onEdit with the series name and offset.
|
|
1202
1203
|
* Returns a cleanup function.
|
|
1203
1204
|
*/
|
|
@@ -1207,7 +1208,7 @@ function wireSeriesLabelDrag(
|
|
|
1207
1208
|
onEdit: (edit: ElementEdit) => void,
|
|
1208
1209
|
setDragging: (dragging: boolean) => void,
|
|
1209
1210
|
): () => void {
|
|
1210
|
-
const labels = svg.querySelectorAll('.
|
|
1211
|
+
const labels = svg.querySelectorAll('.oc-mark-label');
|
|
1211
1212
|
const cleanups: Array<() => void> = [];
|
|
1212
1213
|
|
|
1213
1214
|
// Read existing label offsets from the spec
|
|
@@ -1307,7 +1308,7 @@ function wireLegendInteraction(
|
|
|
1307
1308
|
// Toggle visibility of marks with matching series.
|
|
1308
1309
|
// Uses the data-series attribute set by the SVG renderer, which works
|
|
1309
1310
|
// for all mark types (line, area, rect, arc, point).
|
|
1310
|
-
const marks = svg.querySelectorAll('.
|
|
1311
|
+
const marks = svg.querySelectorAll('.oc-mark');
|
|
1311
1312
|
for (const mark of marks) {
|
|
1312
1313
|
const seriesName = mark.getAttribute('data-series');
|
|
1313
1314
|
if (!seriesName) continue;
|
|
@@ -1367,7 +1368,7 @@ function wireKeyboardNav(
|
|
|
1367
1368
|
function highlightMark(index: number): void {
|
|
1368
1369
|
// Remove previous highlight
|
|
1369
1370
|
if (focusIndex >= 0 && focusIndex < markElements.length) {
|
|
1370
|
-
markElements[focusIndex].classList.remove('
|
|
1371
|
+
markElements[focusIndex].classList.remove('oc-mark-focused');
|
|
1371
1372
|
markElements[focusIndex].removeAttribute('aria-selected');
|
|
1372
1373
|
}
|
|
1373
1374
|
|
|
@@ -1375,7 +1376,7 @@ function wireKeyboardNav(
|
|
|
1375
1376
|
|
|
1376
1377
|
if (focusIndex >= 0 && focusIndex < markElements.length) {
|
|
1377
1378
|
const el = markElements[focusIndex];
|
|
1378
|
-
el.classList.add('
|
|
1379
|
+
el.classList.add('oc-mark-focused');
|
|
1379
1380
|
el.setAttribute('aria-selected', 'true');
|
|
1380
1381
|
}
|
|
1381
1382
|
}
|
|
@@ -1464,7 +1465,7 @@ function createScreenReaderTable(
|
|
|
1464
1465
|
if (!data || data.length === 0) return null;
|
|
1465
1466
|
|
|
1466
1467
|
const table = document.createElement('table');
|
|
1467
|
-
table.className = '
|
|
1468
|
+
table.className = 'oc-sr-only';
|
|
1468
1469
|
// Inline critical SR-only styles so the table stays hidden even when the
|
|
1469
1470
|
// external stylesheet isn't loaded (e.g. CDN / esm.sh usage).
|
|
1470
1471
|
table.style.position = 'absolute';
|
|
@@ -1520,7 +1521,7 @@ function createScreenReaderTable(
|
|
|
1520
1521
|
|
|
1521
1522
|
/** CSS for editable hover feedback, injected into the SVG as a <style> element. */
|
|
1522
1523
|
const EDITABLE_HOVER_CSS = `
|
|
1523
|
-
.
|
|
1524
|
+
.oc-editable-hover {
|
|
1524
1525
|
outline: 1.5px solid rgba(79, 70, 229, 0.35);
|
|
1525
1526
|
outline-offset: 2px;
|
|
1526
1527
|
border-radius: 2px;
|
|
@@ -1564,9 +1565,9 @@ function findElementByRef(svg: SVGElement, ref: ElementRef): SVGElement | null {
|
|
|
1564
1565
|
case 'chrome':
|
|
1565
1566
|
return svg.querySelector(`[data-chrome-key="${ref.key}"]`) as SVGElement | null;
|
|
1566
1567
|
case 'series-label':
|
|
1567
|
-
return svg.querySelector(`.
|
|
1568
|
+
return svg.querySelector(`.oc-mark-label[data-series="${ref.series}"]`) as SVGElement | null;
|
|
1568
1569
|
case 'legend':
|
|
1569
|
-
return svg.querySelector('.
|
|
1570
|
+
return svg.querySelector('.oc-legend') as SVGElement | null;
|
|
1570
1571
|
case 'legend-entry':
|
|
1571
1572
|
return svg.querySelector(`[data-legend-index="${ref.index}"]`) as SVGElement | null;
|
|
1572
1573
|
}
|
|
@@ -1593,7 +1594,7 @@ function buildElementRef(element: Element, _specAnnotations: Annotation[]): Elem
|
|
|
1593
1594
|
}
|
|
1594
1595
|
|
|
1595
1596
|
// Check for series label
|
|
1596
|
-
const seriesLabelEl = element.closest('.
|
|
1597
|
+
const seriesLabelEl = element.closest('.oc-mark-label[data-series]');
|
|
1597
1598
|
if (seriesLabelEl) {
|
|
1598
1599
|
const series = seriesLabelEl.getAttribute('data-series');
|
|
1599
1600
|
if (series) return elementRef.seriesLabel(series);
|
|
@@ -1608,7 +1609,7 @@ function buildElementRef(element: Element, _specAnnotations: Annotation[]): Elem
|
|
|
1608
1609
|
}
|
|
1609
1610
|
|
|
1610
1611
|
// Check for legend group
|
|
1611
|
-
const legendEl = element.closest('.
|
|
1612
|
+
const legendEl = element.closest('.oc-legend');
|
|
1612
1613
|
if (legendEl) return elementRef.legend();
|
|
1613
1614
|
|
|
1614
1615
|
return null;
|
|
@@ -1741,7 +1742,7 @@ function renderSelectionOverlay(
|
|
|
1741
1742
|
const accentColor = layout.theme.colors.categorical?.[0] ?? '#4f46e5';
|
|
1742
1743
|
|
|
1743
1744
|
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
1744
|
-
g.setAttribute('class', '
|
|
1745
|
+
g.setAttribute('class', 'oc-selection-overlay');
|
|
1745
1746
|
|
|
1746
1747
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
1747
1748
|
rect.setAttribute('x', String(bbox.x - padding));
|
|
@@ -1797,6 +1798,10 @@ export function createChart(
|
|
|
1797
1798
|
let pendingRender = false;
|
|
1798
1799
|
let resizeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1799
1800
|
|
|
1801
|
+
// Animation state
|
|
1802
|
+
let isFirstRender = true;
|
|
1803
|
+
let cleanupAnimations: (() => void) | null = null;
|
|
1804
|
+
|
|
1800
1805
|
// Selection and text editing state
|
|
1801
1806
|
let selectedElement: ElementRef | null = options?.selectedElement ?? null;
|
|
1802
1807
|
let overlayElement: SVGGElement | null = null;
|
|
@@ -1967,17 +1972,17 @@ export function createChart(
|
|
|
1967
1972
|
// Hover feedback on editable elements
|
|
1968
1973
|
const handleMouseEnter = (e: Event) => {
|
|
1969
1974
|
const target = (e.target as Element).closest(
|
|
1970
|
-
'[data-annotation-index], [data-chrome-key], .
|
|
1975
|
+
'[data-annotation-index], [data-chrome-key], .oc-mark-label[data-series], .oc-legend, [data-legend-index]',
|
|
1971
1976
|
);
|
|
1972
1977
|
if (target) {
|
|
1973
|
-
(target as SVGElement).classList.add('
|
|
1978
|
+
(target as SVGElement).classList.add('oc-editable-hover');
|
|
1974
1979
|
}
|
|
1975
1980
|
};
|
|
1976
1981
|
|
|
1977
1982
|
const handleMouseLeave = (e: Event) => {
|
|
1978
|
-
const target = (e.target as Element).closest('.
|
|
1983
|
+
const target = (e.target as Element).closest('.oc-editable-hover');
|
|
1979
1984
|
if (target) {
|
|
1980
|
-
(target as SVGElement).classList.remove('
|
|
1985
|
+
(target as SVGElement).classList.remove('oc-editable-hover');
|
|
1981
1986
|
}
|
|
1982
1987
|
};
|
|
1983
1988
|
|
|
@@ -2106,6 +2111,13 @@ export function createChart(
|
|
|
2106
2111
|
return;
|
|
2107
2112
|
}
|
|
2108
2113
|
|
|
2114
|
+
// Cancel any in-progress entrance animations before tearing down
|
|
2115
|
+
if (cleanupAnimations) {
|
|
2116
|
+
cleanupAnimations();
|
|
2117
|
+
cleanupAnimations = null;
|
|
2118
|
+
}
|
|
2119
|
+
cancelAnimations(svgElement);
|
|
2120
|
+
|
|
2109
2121
|
// Clean up previous render
|
|
2110
2122
|
if (cleanupTooltipEvents) {
|
|
2111
2123
|
cleanupTooltipEvents();
|
|
@@ -2161,7 +2173,8 @@ export function createChart(
|
|
|
2161
2173
|
}
|
|
2162
2174
|
|
|
2163
2175
|
currentLayout = compile();
|
|
2164
|
-
|
|
2176
|
+
const shouldAnimate = isFirstRender && !!currentLayout.animation?.enabled;
|
|
2177
|
+
svgElement = renderChartSVG(currentLayout, container, { animate: shouldAnimate });
|
|
2165
2178
|
tooltipManager = createTooltipManager(container);
|
|
2166
2179
|
|
|
2167
2180
|
// Wire tooltip events on mark elements
|
|
@@ -2285,12 +2298,20 @@ export function createChart(
|
|
|
2285
2298
|
srTable = createScreenReaderTable(currentLayout, container);
|
|
2286
2299
|
|
|
2287
2300
|
// Apply container classes for CSS variable scoping and dark mode
|
|
2288
|
-
container.classList.add('
|
|
2301
|
+
container.classList.add('oc-root');
|
|
2289
2302
|
const isDark = resolveDarkMode(options?.darkMode);
|
|
2290
2303
|
if (isDark) {
|
|
2291
|
-
container.classList.add('
|
|
2304
|
+
container.classList.add('oc-dark');
|
|
2292
2305
|
} else {
|
|
2293
|
-
container.classList.remove('
|
|
2306
|
+
container.classList.remove('oc-dark');
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// Set up animation cleanup on first render only.
|
|
2310
|
+
if (shouldAnimate && svgElement) {
|
|
2311
|
+
cleanupAnimations = setupAnimationCleanup(svgElement);
|
|
2312
|
+
}
|
|
2313
|
+
if (isFirstRender) {
|
|
2314
|
+
isFirstRender = false;
|
|
2294
2315
|
}
|
|
2295
2316
|
}
|
|
2296
2317
|
|
|
@@ -2305,6 +2326,12 @@ export function createChart(
|
|
|
2305
2326
|
|
|
2306
2327
|
function resize(): void {
|
|
2307
2328
|
if (destroyed) return;
|
|
2329
|
+
// Skip resize during entrance animation. The resize observer fires
|
|
2330
|
+
// immediately when the container first enters DOM layout, and re-rendering
|
|
2331
|
+
// would destroy the animated SVG. This also blocks genuine resizes during
|
|
2332
|
+
// the animation window (~1s), but catches up on the next resize event
|
|
2333
|
+
// after the cleanup timeout fires.
|
|
2334
|
+
if (cleanupAnimations) return;
|
|
2308
2335
|
render();
|
|
2309
2336
|
}
|
|
2310
2337
|
|
|
@@ -2343,6 +2370,13 @@ export function createChart(
|
|
|
2343
2370
|
if (destroyed) return;
|
|
2344
2371
|
destroyed = true;
|
|
2345
2372
|
|
|
2373
|
+
// Cancel entrance animations
|
|
2374
|
+
if (cleanupAnimations) {
|
|
2375
|
+
cleanupAnimations();
|
|
2376
|
+
cleanupAnimations = null;
|
|
2377
|
+
}
|
|
2378
|
+
cancelAnimations(svgElement);
|
|
2379
|
+
|
|
2346
2380
|
if (resizeTimer !== null) {
|
|
2347
2381
|
clearTimeout(resizeTimer);
|
|
2348
2382
|
resizeTimer = null;
|
|
@@ -2406,8 +2440,8 @@ export function createChart(
|
|
|
2406
2440
|
srTable.parentNode.removeChild(srTable);
|
|
2407
2441
|
srTable = null;
|
|
2408
2442
|
}
|
|
2409
|
-
container.classList.remove('
|
|
2410
|
-
container.classList.remove('
|
|
2443
|
+
container.classList.remove('oc-dark');
|
|
2444
|
+
container.classList.remove('oc-root');
|
|
2411
2445
|
}
|
|
2412
2446
|
|
|
2413
2447
|
// Initial render
|
|
@@ -148,6 +148,7 @@ export function renderTextCell(cell: TextTableCell): HTMLTableCellElement {
|
|
|
148
148
|
/** Render a heatmap-colored cell. */
|
|
149
149
|
export function renderHeatmapCell(cell: HeatmapTableCell): HTMLTableCellElement {
|
|
150
150
|
const td = document.createElement('td');
|
|
151
|
+
td.className = 'oc-table-heatmap';
|
|
151
152
|
td.textContent = cell.formattedValue;
|
|
152
153
|
applyCellStyle(td, cell);
|
|
153
154
|
return td;
|
|
@@ -156,6 +157,7 @@ export function renderHeatmapCell(cell: HeatmapTableCell): HTMLTableCellElement
|
|
|
156
157
|
/** Render a category-colored cell. */
|
|
157
158
|
export function renderCategoryCell(cell: CategoryTableCell): HTMLTableCellElement {
|
|
158
159
|
const td = document.createElement('td');
|
|
160
|
+
td.className = 'oc-table-category';
|
|
159
161
|
td.textContent = cell.formattedValue;
|
|
160
162
|
applyCellStyle(td, cell);
|
|
161
163
|
return td;
|
|
@@ -164,18 +166,18 @@ export function renderCategoryCell(cell: CategoryTableCell): HTMLTableCellElemen
|
|
|
164
166
|
/** Render a cell with an inline bar visualization. */
|
|
165
167
|
export function renderBarCell(cell: BarTableCell): HTMLTableCellElement {
|
|
166
168
|
const td = document.createElement('td');
|
|
167
|
-
td.className = '
|
|
169
|
+
td.className = 'oc-table-bar';
|
|
168
170
|
applyCellStyle(td, cell);
|
|
169
171
|
|
|
170
172
|
const fill = document.createElement('div');
|
|
171
|
-
fill.className = '
|
|
173
|
+
fill.className = 'oc-table-bar-fill';
|
|
172
174
|
fill.style.width = `${Math.round(cell.barWidth * 100)}%`;
|
|
173
175
|
fill.style.left = `${Math.round(cell.barOffset * 100)}%`;
|
|
174
176
|
fill.style.background = cell.barColor;
|
|
175
177
|
td.appendChild(fill);
|
|
176
178
|
|
|
177
179
|
const valueSpan = document.createElement('span');
|
|
178
|
-
valueSpan.className = '
|
|
180
|
+
valueSpan.className = 'oc-table-bar-value';
|
|
179
181
|
valueSpan.textContent = cell.formattedValue;
|
|
180
182
|
td.appendChild(valueSpan);
|
|
181
183
|
|
|
@@ -214,7 +216,7 @@ export function renderSparklineCell(cell: SparklineTableCell): HTMLTableCellElem
|
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
const wrapper = document.createElement('span');
|
|
217
|
-
wrapper.className = '
|
|
219
|
+
wrapper.className = 'oc-table-sparkline';
|
|
218
220
|
|
|
219
221
|
const svgNS = 'http://www.w3.org/2000/svg';
|
|
220
222
|
|
|
@@ -266,14 +268,14 @@ export function renderSparklineCell(cell: SparklineTableCell): HTMLTableCellElem
|
|
|
266
268
|
const dotSize = 5;
|
|
267
269
|
|
|
268
270
|
const startDot = document.createElement('span');
|
|
269
|
-
startDot.className = '
|
|
271
|
+
startDot.className = 'oc-table-sparkline-dot';
|
|
270
272
|
startDot.style.left = '0';
|
|
271
273
|
startDot.style.top = `${firstY - dotSize / 2}px`;
|
|
272
274
|
startDot.style.background = sparklineData.color;
|
|
273
275
|
wrapper.appendChild(startDot);
|
|
274
276
|
|
|
275
277
|
const endDot = document.createElement('span');
|
|
276
|
-
endDot.className = '
|
|
278
|
+
endDot.className = 'oc-table-sparkline-dot';
|
|
277
279
|
endDot.style.right = '0';
|
|
278
280
|
endDot.style.top = `${lastY - dotSize / 2}px`;
|
|
279
281
|
endDot.style.background = sparklineData.color;
|
|
@@ -281,7 +283,7 @@ export function renderSparklineCell(cell: SparklineTableCell): HTMLTableCellElem
|
|
|
281
283
|
|
|
282
284
|
// HTML labels below the SVG, positioned at left and right edges
|
|
283
285
|
const labelsRow = document.createElement('span');
|
|
284
|
-
labelsRow.className = '
|
|
286
|
+
labelsRow.className = 'oc-table-sparkline-labels';
|
|
285
287
|
labelsRow.style.color = sparklineData.color;
|
|
286
288
|
|
|
287
289
|
const startLabel = document.createElement('span');
|
|
@@ -378,7 +380,7 @@ export function renderImageCell(cell: ImageTableCell): HTMLTableCellElement {
|
|
|
378
380
|
applyCellStyle(td, cell);
|
|
379
381
|
|
|
380
382
|
const wrapper = document.createElement('span');
|
|
381
|
-
wrapper.className = `
|
|
383
|
+
wrapper.className = `oc-table-image${cell.rounded ? ' oc-table-image-rounded' : ''}`;
|
|
382
384
|
|
|
383
385
|
const img = document.createElement('img');
|
|
384
386
|
img.src = cell.src;
|
|
@@ -399,7 +401,7 @@ export function renderFlagCell(cell: FlagTableCell): HTMLTableCellElement {
|
|
|
399
401
|
applyCellStyle(td, cell);
|
|
400
402
|
|
|
401
403
|
const span = document.createElement('span');
|
|
402
|
-
span.className = '
|
|
404
|
+
span.className = 'oc-table-flag';
|
|
403
405
|
span.setAttribute('role', 'img');
|
|
404
406
|
|
|
405
407
|
if (cell.countryCode && cell.countryCode.length === 2) {
|