@unovis/mcp 1.7.0-mcp.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/LICENSE +193 -0
- package/README.md +154 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/index.d.ts +14 -0
- package/dist/codegen/index.js +346 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/env/bbox.d.ts +13 -0
- package/dist/env/bbox.js +335 -0
- package/dist/env/bbox.js.map +1 -0
- package/dist/env/canvas.d.ts +9 -0
- package/dist/env/canvas.js +55 -0
- package/dist/env/canvas.js.map +1 -0
- package/dist/env/computed-style.d.ts +38 -0
- package/dist/env/computed-style.js +186 -0
- package/dist/env/computed-style.js.map +1 -0
- package/dist/env/fonts.d.ts +3 -0
- package/dist/env/fonts.js +103 -0
- package/dist/env/fonts.js.map +1 -0
- package/dist/env/index.d.ts +13 -0
- package/dist/env/index.js +121 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/raf-queue.d.ts +23 -0
- package/dist/env/raf-queue.js +51 -0
- package/dist/env/raf-queue.js.map +1 -0
- package/dist/env/size.d.ts +9 -0
- package/dist/env/size.js +30 -0
- package/dist/env/size.js.map +1 -0
- package/dist/html/document.d.ts +16 -0
- package/dist/html/document.js +95 -0
- package/dist/html/document.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/recipes/area.d.ts +66 -0
- package/dist/recipes/area.js +76 -0
- package/dist/recipes/area.js.map +1 -0
- package/dist/recipes/bar.d.ts +67 -0
- package/dist/recipes/bar.js +69 -0
- package/dist/recipes/bar.js.map +1 -0
- package/dist/recipes/boxplot.d.ts +64 -0
- package/dist/recipes/boxplot.js +89 -0
- package/dist/recipes/boxplot.js.map +1 -0
- package/dist/recipes/chord.d.ts +41 -0
- package/dist/recipes/chord.js +72 -0
- package/dist/recipes/chord.js.map +1 -0
- package/dist/recipes/choropleth-regions.d.ts +49 -0
- package/dist/recipes/choropleth-regions.js +521 -0
- package/dist/recipes/choropleth-regions.js.map +1 -0
- package/dist/recipes/choropleth.d.ts +27 -0
- package/dist/recipes/choropleth.js +86 -0
- package/dist/recipes/choropleth.js.map +1 -0
- package/dist/recipes/donut.d.ts +25 -0
- package/dist/recipes/donut.js +51 -0
- package/dist/recipes/donut.js.map +1 -0
- package/dist/recipes/graph.d.ts +55 -0
- package/dist/recipes/graph.js +132 -0
- package/dist/recipes/graph.js.map +1 -0
- package/dist/recipes/heatmap.d.ts +21 -0
- package/dist/recipes/heatmap.js +79 -0
- package/dist/recipes/heatmap.js.map +1 -0
- package/dist/recipes/index.d.ts +5 -0
- package/dist/recipes/index.js +35 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/line.d.ts +69 -0
- package/dist/recipes/line.js +64 -0
- package/dist/recipes/line.js.map +1 -0
- package/dist/recipes/nested-donut.d.ts +22 -0
- package/dist/recipes/nested-donut.js +47 -0
- package/dist/recipes/nested-donut.js.map +1 -0
- package/dist/recipes/radial-bar.d.ts +25 -0
- package/dist/recipes/radial-bar.js +53 -0
- package/dist/recipes/radial-bar.js.map +1 -0
- package/dist/recipes/sankey.d.ts +41 -0
- package/dist/recipes/sankey.js +61 -0
- package/dist/recipes/sankey.js.map +1 -0
- package/dist/recipes/scatter.d.ts +68 -0
- package/dist/recipes/scatter.js +75 -0
- package/dist/recipes/scatter.js.map +1 -0
- package/dist/recipes/shared.d.ts +144 -0
- package/dist/recipes/shared.js +212 -0
- package/dist/recipes/shared.js.map +1 -0
- package/dist/recipes/timeline.d.ts +31 -0
- package/dist/recipes/timeline.js +123 -0
- package/dist/recipes/timeline.js.map +1 -0
- package/dist/recipes/treemap.d.ts +19 -0
- package/dist/recipes/treemap.js +43 -0
- package/dist/recipes/treemap.js.map +1 -0
- package/dist/recipes/types.d.ts +16 -0
- package/dist/recipes/types.js +2 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/render/headless.d.ts +62 -0
- package/dist/render/headless.js +158 -0
- package/dist/render/headless.js.map +1 -0
- package/dist/render/materialize.d.ts +35 -0
- package/dist/render/materialize.js +160 -0
- package/dist/render/materialize.js.map +1 -0
- package/dist/render/mutex.d.ts +9 -0
- package/dist/render/mutex.js +14 -0
- package/dist/render/mutex.js.map +1 -0
- package/dist/render/rasterize.d.ts +10 -0
- package/dist/render/rasterize.js +29 -0
- package/dist/render/rasterize.js.map +1 -0
- package/dist/render/renderer.d.ts +21 -0
- package/dist/render/renderer.js +66 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/render/spec.d.ts +95 -0
- package/dist/render/spec.js +15 -0
- package/dist/render/spec.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +44 -0
- package/dist/server.js.map +1 -0
- package/dist/svg/collect-css.d.ts +14 -0
- package/dist/svg/collect-css.js +53 -0
- package/dist/svg/collect-css.js.map +1 -0
- package/dist/svg/css-vars.d.ts +17 -0
- package/dist/svg/css-vars.js +64 -0
- package/dist/svg/css-vars.js.map +1 -0
- package/dist/svg/header.d.ts +8 -0
- package/dist/svg/header.js +81 -0
- package/dist/svg/header.js.map +1 -0
- package/dist/svg/ids.d.ts +12 -0
- package/dist/svg/ids.js +94 -0
- package/dist/svg/ids.js.map +1 -0
- package/dist/svg/postprocess.d.ts +41 -0
- package/dist/svg/postprocess.js +135 -0
- package/dist/svg/postprocess.js.map +1 -0
- package/dist/tools/register.d.ts +12 -0
- package/dist/tools/register.js +127 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/widget/bundle.js +1777 -0
- package/dist/widget/bundle.meta.json +3 -0
- package/dist/widget/entry.d.ts +34 -0
- package/dist/widget/entry.js +118 -0
- package/dist/widget/entry.js.map +1 -0
- package/dist/widget/interactions.d.ts +14 -0
- package/dist/widget/interactions.js +151 -0
- package/dist/widget/interactions.js.map +1 -0
- package/dist/widget/unovis-slim.d.ts +33 -0
- package/dist/widget/unovis-slim.js +34 -0
- package/dist/widget/unovis-slim.js.map +1 -0
- package/docs/README.md +56 -0
- package/docs/architecture.md +158 -0
- package/docs/chart-spec.md +148 -0
- package/docs/getting-started.md +175 -0
- package/docs/interactive.md +125 -0
- package/docs/output-types.md +170 -0
- package/docs/programmatic.md +234 -0
- package/docs/tools.md +273 -0
- package/docs/troubleshooting.md +140 -0
- package/fonts/README.md +19 -0
- package/package.json +73 -0
package/dist/env/bbox.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/** SVG geometry polyfills for jsdom.
|
|
2
|
+
*
|
|
3
|
+
* jsdom implements no SVG layout: `getBBox`, `getBoundingClientRect` (for SVG
|
|
4
|
+
* content) and `getComputedTextLength` are missing or return zeros. Unovis
|
|
5
|
+
* relies on them for axis auto-margins, label trimming/placement and overlap
|
|
6
|
+
* detection. This module implements them with attribute math + real canvas
|
|
7
|
+
* text metrics.
|
|
8
|
+
*
|
|
9
|
+
* Note: jsdom instantiates most SVG tags as plain `SVGElement`, so the
|
|
10
|
+
* polyfills are installed on `SVGElement.prototype` with tag-based dispatch.
|
|
11
|
+
*/
|
|
12
|
+
import pathBounds from 'svg-path-bounds';
|
|
13
|
+
import { svgPathProperties as SvgPathProperties } from 'svg-path-properties';
|
|
14
|
+
import { measureTextWidth } from './canvas.js';
|
|
15
|
+
import { resolveFontShorthand } from './computed-style.js';
|
|
16
|
+
const EMPTY_BOX = { x: 0, y: 0, width: 0, height: 0 };
|
|
17
|
+
const NON_RENDERED_TAGS = new Set(['defs', 'clippath', 'mask', 'marker', 'pattern', 'symbol', 'title', 'desc', 'style', 'script', 'filter']);
|
|
18
|
+
const LINE_HEIGHT = 1.2;
|
|
19
|
+
const ASCENT_RATIO = 0.8;
|
|
20
|
+
const num = (el, attr, fallback = 0) => {
|
|
21
|
+
const value = parseFloat(el.getAttribute(attr) ?? '');
|
|
22
|
+
return Number.isFinite(value) ? value : fallback;
|
|
23
|
+
};
|
|
24
|
+
/** Parse em/px length attributes like dy="1.25em" relative to a font size */
|
|
25
|
+
const lengthToPx = (value, fontSizePx) => {
|
|
26
|
+
if (!value)
|
|
27
|
+
return 0;
|
|
28
|
+
const parsed = parseFloat(value);
|
|
29
|
+
if (!Number.isFinite(parsed))
|
|
30
|
+
return 0;
|
|
31
|
+
return value.trim().endsWith('em') ? parsed * fontSizePx : parsed;
|
|
32
|
+
};
|
|
33
|
+
/** Resolve an inherited presentation attribute (attribute or inline style)
|
|
34
|
+
* through the ancestor chain — e.g. d3-axis sets text-anchor on the axis
|
|
35
|
+
* <g>, not on the tick <text> elements. */
|
|
36
|
+
const inheritedAttr = (el, name) => {
|
|
37
|
+
let node = el;
|
|
38
|
+
while (node && node.namespaceURI?.endsWith('svg')) {
|
|
39
|
+
const attr = node.getAttribute(name);
|
|
40
|
+
if (attr)
|
|
41
|
+
return attr;
|
|
42
|
+
const inline = node.style?.getPropertyValue(name);
|
|
43
|
+
if (inline)
|
|
44
|
+
return inline;
|
|
45
|
+
node = node.parentElement;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
const IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
50
|
+
const multiply = (m1, m2) => ([
|
|
51
|
+
m1[0] * m2[0] + m1[2] * m2[1],
|
|
52
|
+
m1[1] * m2[0] + m1[3] * m2[1],
|
|
53
|
+
m1[0] * m2[2] + m1[2] * m2[3],
|
|
54
|
+
m1[1] * m2[2] + m1[3] * m2[3],
|
|
55
|
+
m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
|
|
56
|
+
m1[1] * m2[4] + m1[3] * m2[5] + m1[5],
|
|
57
|
+
]);
|
|
58
|
+
export function parseTransform(transform) {
|
|
59
|
+
if (!transform)
|
|
60
|
+
return IDENTITY;
|
|
61
|
+
let matrix = IDENTITY;
|
|
62
|
+
const fnRegex = /(translate|scale|rotate|matrix)\s*\(([^)]*)\)/g;
|
|
63
|
+
for (const match of transform.matchAll(fnRegex)) {
|
|
64
|
+
const args = match[2].split(/[\s,]+/).filter(s => s.length).map(Number);
|
|
65
|
+
switch (match[1]) {
|
|
66
|
+
case 'translate':
|
|
67
|
+
matrix = multiply(matrix, [1, 0, 0, 1, args[0] || 0, args[1] || 0]);
|
|
68
|
+
break;
|
|
69
|
+
case 'scale':
|
|
70
|
+
matrix = multiply(matrix, [args[0] ?? 1, 0, 0, args[1] ?? args[0] ?? 1, 0, 0]);
|
|
71
|
+
break;
|
|
72
|
+
case 'rotate': {
|
|
73
|
+
const angle = (args[0] || 0) * Math.PI / 180;
|
|
74
|
+
const [cx, cy] = [args[1] || 0, args[2] || 0];
|
|
75
|
+
const [cos, sin] = [Math.cos(angle), Math.sin(angle)];
|
|
76
|
+
matrix = multiply(matrix, [1, 0, 0, 1, cx, cy]);
|
|
77
|
+
matrix = multiply(matrix, [cos, sin, -sin, cos, 0, 0]);
|
|
78
|
+
matrix = multiply(matrix, [1, 0, 0, 1, -cx, -cy]);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 'matrix':
|
|
82
|
+
if (args.length === 6)
|
|
83
|
+
matrix = multiply(matrix, args);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return matrix;
|
|
88
|
+
}
|
|
89
|
+
const transformBox = (box, matrix) => {
|
|
90
|
+
if (matrix === IDENTITY)
|
|
91
|
+
return box;
|
|
92
|
+
const corners = [
|
|
93
|
+
[box.x, box.y],
|
|
94
|
+
[box.x + box.width, box.y],
|
|
95
|
+
[box.x, box.y + box.height],
|
|
96
|
+
[box.x + box.width, box.y + box.height],
|
|
97
|
+
];
|
|
98
|
+
let [minX, minY, maxX, maxY] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
99
|
+
for (const [x, y] of corners) {
|
|
100
|
+
const tx = matrix[0] * x + matrix[2] * y + matrix[4];
|
|
101
|
+
const ty = matrix[1] * x + matrix[3] * y + matrix[5];
|
|
102
|
+
minX = Math.min(minX, tx);
|
|
103
|
+
minY = Math.min(minY, ty);
|
|
104
|
+
maxX = Math.max(maxX, tx);
|
|
105
|
+
maxY = Math.max(maxY, ty);
|
|
106
|
+
}
|
|
107
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
108
|
+
};
|
|
109
|
+
/** Flatten a <text> element into positioned lines. Unovis's
|
|
110
|
+
* renderTextToTspanElements produces <text><tspan y? font-size?><tspan x dy
|
|
111
|
+
* dominant-baseline>line</tspan>…</tspan></text> — the y offset lives on the
|
|
112
|
+
* first block tspan and lines advance via em-based dy on the inner tspans. */
|
|
113
|
+
function collectTextLines(el, fontSizePx) {
|
|
114
|
+
const ownX = num(el, 'x');
|
|
115
|
+
const defaultBaseline = inheritedAttr(el, 'dominant-baseline') ?? 'auto';
|
|
116
|
+
const blocks = Array.from(el.children).filter(c => c.localName === 'tspan');
|
|
117
|
+
if (!blocks.length) {
|
|
118
|
+
return {
|
|
119
|
+
lines: [{ text: el.textContent ?? '', x: ownX, dyPx: lengthToPx(el.getAttribute('dy'), fontSizePx), baseline: defaultBaseline }],
|
|
120
|
+
startY: num(el, 'y'),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
let startY = parseFloat(el.getAttribute('y') ?? '');
|
|
124
|
+
const lines = [];
|
|
125
|
+
for (const block of blocks) {
|
|
126
|
+
if (!Number.isFinite(startY)) {
|
|
127
|
+
const blockY = parseFloat(block.getAttribute('y') ?? '');
|
|
128
|
+
if (Number.isFinite(blockY))
|
|
129
|
+
startY = blockY;
|
|
130
|
+
}
|
|
131
|
+
const blockFontSize = parseFloat(block.getAttribute('font-size') ?? '') || fontSizePx;
|
|
132
|
+
const lineTspans = Array.from(block.children).filter(c => c.localName === 'tspan');
|
|
133
|
+
const items = lineTspans.length ? lineTspans : [block];
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
lines.push({
|
|
136
|
+
text: item.textContent ?? '',
|
|
137
|
+
x: num(item, 'x', num(block, 'x', ownX)),
|
|
138
|
+
dyPx: lengthToPx(item.getAttribute('dy'), blockFontSize),
|
|
139
|
+
baseline: item.getAttribute('dominant-baseline') ?? defaultBaseline,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { lines, startY: Number.isFinite(startY) ? startY : 0 };
|
|
144
|
+
}
|
|
145
|
+
function textBox(el, document) {
|
|
146
|
+
const { font, fontSizePx } = resolveFontShorthand(el, document);
|
|
147
|
+
const anchor = inheritedAttr(el, 'text-anchor');
|
|
148
|
+
const { lines, startY } = collectTextLines(el, fontSizePx);
|
|
149
|
+
let minLeft = Infinity;
|
|
150
|
+
let maxRight = -Infinity;
|
|
151
|
+
let top = Infinity;
|
|
152
|
+
let bottom = -Infinity;
|
|
153
|
+
let baselineY = startY;
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
baselineY += line.dyPx;
|
|
156
|
+
const lineWidth = measureTextWidth(line.text, font);
|
|
157
|
+
let left = line.x;
|
|
158
|
+
if (anchor === 'middle')
|
|
159
|
+
left -= lineWidth / 2;
|
|
160
|
+
else if (anchor === 'end')
|
|
161
|
+
left -= lineWidth;
|
|
162
|
+
minLeft = Math.min(minLeft, left);
|
|
163
|
+
maxRight = Math.max(maxRight, left + lineWidth);
|
|
164
|
+
// Map the baseline position to the line's top edge
|
|
165
|
+
let lineTop;
|
|
166
|
+
if (line.baseline === 'central' || line.baseline === 'middle')
|
|
167
|
+
lineTop = baselineY - fontSizePx * 0.6;
|
|
168
|
+
else if (line.baseline === 'hanging' || line.baseline === 'text-before-edge')
|
|
169
|
+
lineTop = baselineY;
|
|
170
|
+
else
|
|
171
|
+
lineTop = baselineY - fontSizePx * ASCENT_RATIO;
|
|
172
|
+
top = Math.min(top, lineTop);
|
|
173
|
+
bottom = Math.max(bottom, lineTop + fontSizePx * LINE_HEIGHT);
|
|
174
|
+
}
|
|
175
|
+
if (!Number.isFinite(minLeft))
|
|
176
|
+
return EMPTY_BOX;
|
|
177
|
+
return { x: minLeft, y: top, width: maxRight - minLeft, height: bottom - top };
|
|
178
|
+
}
|
|
179
|
+
function pointsBox(el) {
|
|
180
|
+
const values = (el.getAttribute('points') ?? '').split(/[\s,]+/).filter(s => s.length).map(Number);
|
|
181
|
+
if (values.length < 2)
|
|
182
|
+
return EMPTY_BOX;
|
|
183
|
+
let [minX, minY, maxX, maxY] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
184
|
+
for (let i = 0; i + 1 < values.length; i += 2) {
|
|
185
|
+
minX = Math.min(minX, values[i]);
|
|
186
|
+
maxX = Math.max(maxX, values[i]);
|
|
187
|
+
minY = Math.min(minY, values[i + 1]);
|
|
188
|
+
maxY = Math.max(maxY, values[i + 1]);
|
|
189
|
+
}
|
|
190
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
191
|
+
}
|
|
192
|
+
function pathBox(el) {
|
|
193
|
+
const d = el.getAttribute('d');
|
|
194
|
+
if (!d)
|
|
195
|
+
return EMPTY_BOX;
|
|
196
|
+
try {
|
|
197
|
+
const [left, top, right, bottom] = pathBounds(d);
|
|
198
|
+
if (![left, top, right, bottom].every(Number.isFinite))
|
|
199
|
+
return EMPTY_BOX;
|
|
200
|
+
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return EMPTY_BOX;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function unionBox(el, document) {
|
|
207
|
+
let [minX, minY, maxX, maxY] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
208
|
+
for (const child of Array.from(el.children)) {
|
|
209
|
+
if (NON_RENDERED_TAGS.has(child.localName))
|
|
210
|
+
continue;
|
|
211
|
+
if (child.getAttribute('display') === 'none')
|
|
212
|
+
continue;
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
214
|
+
const box = computeBox(child, document);
|
|
215
|
+
if (box.width === 0 && box.height === 0)
|
|
216
|
+
continue;
|
|
217
|
+
const transformed = transformBox(box, parseTransform(child.getAttribute('transform')));
|
|
218
|
+
minX = Math.min(minX, transformed.x);
|
|
219
|
+
minY = Math.min(minY, transformed.y);
|
|
220
|
+
maxX = Math.max(maxX, transformed.x + transformed.width);
|
|
221
|
+
maxY = Math.max(maxY, transformed.y + transformed.height);
|
|
222
|
+
}
|
|
223
|
+
if (!Number.isFinite(minX))
|
|
224
|
+
return EMPTY_BOX;
|
|
225
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
226
|
+
}
|
|
227
|
+
export function computeBox(el, document) {
|
|
228
|
+
switch (el.localName) {
|
|
229
|
+
case 'text':
|
|
230
|
+
case 'tspan':
|
|
231
|
+
return textBox(el, document);
|
|
232
|
+
case 'rect':
|
|
233
|
+
case 'image':
|
|
234
|
+
case 'foreignobject':
|
|
235
|
+
return { x: num(el, 'x'), y: num(el, 'y'), width: num(el, 'width'), height: num(el, 'height') };
|
|
236
|
+
case 'line': {
|
|
237
|
+
const [x1, y1, x2, y2] = [num(el, 'x1'), num(el, 'y1'), num(el, 'x2'), num(el, 'y2')];
|
|
238
|
+
return { x: Math.min(x1, x2), y: Math.min(y1, y2), width: Math.abs(x2 - x1), height: Math.abs(y2 - y1) };
|
|
239
|
+
}
|
|
240
|
+
case 'circle': {
|
|
241
|
+
const r = num(el, 'r');
|
|
242
|
+
return { x: num(el, 'cx') - r, y: num(el, 'cy') - r, width: 2 * r, height: 2 * r };
|
|
243
|
+
}
|
|
244
|
+
case 'ellipse': {
|
|
245
|
+
const [rx, ry] = [num(el, 'rx'), num(el, 'ry')];
|
|
246
|
+
return { x: num(el, 'cx') - rx, y: num(el, 'cy') - ry, width: 2 * rx, height: 2 * ry };
|
|
247
|
+
}
|
|
248
|
+
case 'polyline':
|
|
249
|
+
case 'polygon':
|
|
250
|
+
return pointsBox(el);
|
|
251
|
+
case 'path':
|
|
252
|
+
return pathBox(el);
|
|
253
|
+
default:
|
|
254
|
+
return unionBox(el, document);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ── installation ────────────────────────────────────────────────────────────
|
|
258
|
+
export function installBBoxPolyfills(window) {
|
|
259
|
+
const document = window.document;
|
|
260
|
+
const proto = window.SVGElement.prototype;
|
|
261
|
+
// SVGAnimatedLength stubs — d3-zoom's defaultExtent reads
|
|
262
|
+
// svg.width.baseVal.value (used by Graph's fit-to-view)
|
|
263
|
+
for (const prop of ['width', 'height']) {
|
|
264
|
+
Object.defineProperty(window.SVGElement.prototype, prop, {
|
|
265
|
+
configurable: true,
|
|
266
|
+
get() {
|
|
267
|
+
return { baseVal: { value: parseFloat(this.getAttribute(prop) ?? '') || 0 } };
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
proto.getBBox = function () {
|
|
272
|
+
return computeBox(this, document);
|
|
273
|
+
};
|
|
274
|
+
proto.getComputedTextLength = function () {
|
|
275
|
+
const { font } = resolveFontShorthand(this, document);
|
|
276
|
+
return measureTextWidth(this.textContent ?? '', font);
|
|
277
|
+
};
|
|
278
|
+
// Path geometry (Graph links, TopoJSON flows measure their paths)
|
|
279
|
+
const pathPropsCache = new Map();
|
|
280
|
+
const pathProps = (el) => {
|
|
281
|
+
const d = el.getAttribute('d');
|
|
282
|
+
// NaN coordinates would send path-properties' numeric integration into
|
|
283
|
+
// an endless loop — treat such paths as unmeasurable
|
|
284
|
+
if (!d || d.includes('NaN'))
|
|
285
|
+
return undefined;
|
|
286
|
+
let props = pathPropsCache.get(d);
|
|
287
|
+
if (!props) {
|
|
288
|
+
try {
|
|
289
|
+
props = new SvgPathProperties(d);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
if (pathPropsCache.size > 500)
|
|
295
|
+
pathPropsCache.clear();
|
|
296
|
+
pathPropsCache.set(d, props);
|
|
297
|
+
}
|
|
298
|
+
return props;
|
|
299
|
+
};
|
|
300
|
+
const protoWithPathMethods = proto;
|
|
301
|
+
protoWithPathMethods.getTotalLength = function () {
|
|
302
|
+
return pathProps(this)?.getTotalLength() ?? 0;
|
|
303
|
+
};
|
|
304
|
+
protoWithPathMethods.getPointAtLength = function (length) {
|
|
305
|
+
const point = pathProps(this)?.getPointAtLength(length);
|
|
306
|
+
return point ? { x: point.x, y: point.y } : { x: 0, y: 0 };
|
|
307
|
+
};
|
|
308
|
+
// Overlap detection (utils/text-overlap.ts) and a few components measure
|
|
309
|
+
// labels via getBoundingClientRect — reconstruct it from local geometry
|
|
310
|
+
// mapped through ancestor transforms (the chart sits at the page origin).
|
|
311
|
+
Object.defineProperty(window.SVGElement.prototype, 'getBoundingClientRect', {
|
|
312
|
+
configurable: true,
|
|
313
|
+
value: function () {
|
|
314
|
+
let box = computeBox(this, document);
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
316
|
+
let node = this;
|
|
317
|
+
while (node && node.localName !== 'svg') {
|
|
318
|
+
box = transformBox(box, parseTransform(node.getAttribute('transform')));
|
|
319
|
+
node = node.parentElement;
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
x: box.x,
|
|
323
|
+
y: box.y,
|
|
324
|
+
width: box.width,
|
|
325
|
+
height: box.height,
|
|
326
|
+
top: box.y,
|
|
327
|
+
left: box.x,
|
|
328
|
+
right: box.x + box.width,
|
|
329
|
+
bottom: box.y + box.height,
|
|
330
|
+
toJSON: () => box,
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=bbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bbox.js","sourceRoot":"","sources":["../../src/env/bbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SKRSContext2D } from '@napi-rs/canvas';
|
|
2
|
+
import type { DOMWindow } from 'jsdom';
|
|
3
|
+
export declare function getSharedContext(): SKRSContext2D;
|
|
4
|
+
/** Register every font file in a directory. The family name is derived from
|
|
5
|
+
* the file name: "Inter-Regular.ttf" → "Inter". */
|
|
6
|
+
export declare function registerFontsFromDir(dir: string): string[];
|
|
7
|
+
/** Measure text width with the shared context. `font` is a CSS font shorthand. */
|
|
8
|
+
export declare function measureTextWidth(text: string, font: string): number;
|
|
9
|
+
export declare function installCanvasHook(window: DOMWindow): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Canvas 2D support for text measurement.
|
|
2
|
+
*
|
|
3
|
+
* `@unovis/ts` snapshots `document.createElement('canvas').getContext('2d')`
|
|
4
|
+
* at module load (utils/text-measure.ts) and uses `ctx.measureText` for all
|
|
5
|
+
* precise text measurement. jsdom has no canvas backend, so we patch
|
|
6
|
+
* `HTMLCanvasElement.prototype.getContext` to hand out a real 2D context from
|
|
7
|
+
* @napi-rs/canvas (prebuilt Skia, no postinstall scripts).
|
|
8
|
+
*
|
|
9
|
+
* Fonts are provisioned by env/fonts.ts and registered via
|
|
10
|
+
* registerFontsFromDir so text metrics match the font declared in the
|
|
11
|
+
* output (Unovis defaults to Inter).
|
|
12
|
+
*/
|
|
13
|
+
import { createCanvas, GlobalFonts } from '@napi-rs/canvas';
|
|
14
|
+
import { readdirSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
let sharedContext;
|
|
17
|
+
export function getSharedContext() {
|
|
18
|
+
if (!sharedContext)
|
|
19
|
+
sharedContext = createCanvas(512, 128).getContext('2d');
|
|
20
|
+
return sharedContext;
|
|
21
|
+
}
|
|
22
|
+
/** Register every font file in a directory. The family name is derived from
|
|
23
|
+
* the file name: "Inter-Regular.ttf" → "Inter". */
|
|
24
|
+
export function registerFontsFromDir(dir) {
|
|
25
|
+
const registered = [];
|
|
26
|
+
let entries = [];
|
|
27
|
+
try {
|
|
28
|
+
entries = readdirSync(dir);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return registered;
|
|
32
|
+
}
|
|
33
|
+
for (const file of entries) {
|
|
34
|
+
if (!/\.(ttf|otf|woff2?)$/i.test(file))
|
|
35
|
+
continue;
|
|
36
|
+
const family = file.replace(/\.(ttf|otf|woff2?)$/i, '').split('-')[0];
|
|
37
|
+
if (GlobalFonts.registerFromPath(join(dir, file), family))
|
|
38
|
+
registered.push(file);
|
|
39
|
+
}
|
|
40
|
+
return registered;
|
|
41
|
+
}
|
|
42
|
+
/** Measure text width with the shared context. `font` is a CSS font shorthand. */
|
|
43
|
+
export function measureTextWidth(text, font) {
|
|
44
|
+
const ctx = getSharedContext();
|
|
45
|
+
if (ctx.font !== font)
|
|
46
|
+
ctx.font = font;
|
|
47
|
+
return ctx.measureText(text).width;
|
|
48
|
+
}
|
|
49
|
+
export function installCanvasHook(window) {
|
|
50
|
+
const proto = window.HTMLCanvasElement.prototype;
|
|
51
|
+
proto.getContext = function (type) {
|
|
52
|
+
return type === '2d' ? getSharedContext() : null;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=canvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.js","sourceRoot":"","sources":["../../src/env/canvas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAIhC,IAAI,aAAwC,CAAA;AAE5C,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC,aAAa;QAAE,aAAa,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;IAC3E,OAAO,aAAa,CAAA;AACtB,CAAC;AAED;mDACmD;AACnD,MAAM,UAAU,oBAAoB,CAAE,GAAW;IAC/C,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,IAAI,OAAO,GAAa,EAAE,CAAA;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACrE,IAAI,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClF,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAE,IAAY,EAAE,IAAY;IAC1D,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAA;IAC9B,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IACtC,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAE,MAAiB;IAClD,MAAM,KAAK,GAAG,MAAM,CAAC,iBAAiB,CAAC,SAAsD,CAAA;IAC7F,KAAK,CAAC,UAAU,GAAG,UAAU,IAAY;QACvC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAClD,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** `getComputedStyle` wrapper for jsdom.
|
|
2
|
+
*
|
|
3
|
+
* Unovis resolves its theming through CSS custom properties
|
|
4
|
+
* (`getCSSVariableValue` → `getComputedStyle(el).getPropertyValue('--vis-*')`)
|
|
5
|
+
* and reads font information for text measurement
|
|
6
|
+
* (`utils/text-measure.ts#getFontInfo`). jsdom neither cascades custom
|
|
7
|
+
* properties from stylesheets nor resolves fonts, so this wrapper:
|
|
8
|
+
*
|
|
9
|
+
* - resolves `--*` properties from the element's inline-style chain, then
|
|
10
|
+
* from a variable map parsed out of the emotion-injected stylesheets
|
|
11
|
+
* (with dark-theme overrides applied when the dark theme is active)
|
|
12
|
+
* - synthesizes font properties (size/family/weight/style) from element
|
|
13
|
+
* attributes, inline styles, and Unovis defaults, resolving `var()`
|
|
14
|
+
* references in the values
|
|
15
|
+
*/
|
|
16
|
+
import type { DOMWindow } from 'jsdom';
|
|
17
|
+
export interface VarMaps {
|
|
18
|
+
light: Map<string, string>;
|
|
19
|
+
dark: Map<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function setVarMaps(maps: VarMaps): void;
|
|
22
|
+
export declare function setFontRules(rules: {
|
|
23
|
+
selector: string;
|
|
24
|
+
block: string;
|
|
25
|
+
}[]): void;
|
|
26
|
+
export declare function getVarMaps(): VarMaps;
|
|
27
|
+
/** Matches the selectors in @unovis/ts utils/theme.ts */
|
|
28
|
+
export declare function isDarkThemeActive(document: Document): boolean;
|
|
29
|
+
/** Substitute var(--name[, fallback]) references in a CSS value using the active maps */
|
|
30
|
+
export declare function substituteVars(value: string, document: Document, depth?: number): string;
|
|
31
|
+
/** Resolve a custom property for an element: inline chain first, then the theme maps */
|
|
32
|
+
export declare function resolveCssVar(name: string, el: Element | null, document: Document): string;
|
|
33
|
+
/** Resolve the CSS font shorthand used for canvas text measurement */
|
|
34
|
+
export declare function resolveFontShorthand(el: Element, document: Document): {
|
|
35
|
+
font: string;
|
|
36
|
+
fontSizePx: number;
|
|
37
|
+
};
|
|
38
|
+
export declare function installComputedStyle(window: DOMWindow): void;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const FALLBACK_FONT_FAMILY = 'Inter, Arial, "Helvetica Neue", Helvetica, sans-serif';
|
|
2
|
+
const FALLBACK_FONT_SIZE = '12px';
|
|
3
|
+
let varMaps = { light: new Map(), dark: new Map() };
|
|
4
|
+
let fontRules = [];
|
|
5
|
+
export function setVarMaps(maps) {
|
|
6
|
+
varMaps = maps;
|
|
7
|
+
}
|
|
8
|
+
export function setFontRules(rules) {
|
|
9
|
+
fontRules = [];
|
|
10
|
+
for (const rule of rules) {
|
|
11
|
+
if (!rule.block.includes('font-'))
|
|
12
|
+
continue;
|
|
13
|
+
if (rule.selector.startsWith('@'))
|
|
14
|
+
continue;
|
|
15
|
+
// :root blocks hold custom-property definitions (e.g.
|
|
16
|
+
// --vis-icon-font-family) — never real font declarations. Without this,
|
|
17
|
+
// `html.matches(':root')` would leak icon fonts into every element whose
|
|
18
|
+
// own classes declare no font.
|
|
19
|
+
if (rule.selector.includes(':root'))
|
|
20
|
+
continue;
|
|
21
|
+
const declarations = new Map();
|
|
22
|
+
// The lookbehind keeps custom properties like --vis-*-font-size from
|
|
23
|
+
// matching as `font-size`
|
|
24
|
+
for (const match of rule.block.matchAll(/(?<![-\w])(font-(?:size|family|weight|style))\s*:\s*([^;}]+)/g)) {
|
|
25
|
+
declarations.set(match[1], match[2].trim());
|
|
26
|
+
}
|
|
27
|
+
if (declarations.size)
|
|
28
|
+
fontRules.push({ selector: rule.selector, declarations });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function matchFontRules(el, prop) {
|
|
32
|
+
let value;
|
|
33
|
+
for (const rule of fontRules) {
|
|
34
|
+
const declared = rule.declarations.get(prop);
|
|
35
|
+
if (declared === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
if (el.matches(rule.selector))
|
|
39
|
+
value = declared; // later rules win, like source order
|
|
40
|
+
}
|
|
41
|
+
catch { /* selector not supported by jsdom */ }
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
export function getVarMaps() {
|
|
46
|
+
return varMaps;
|
|
47
|
+
}
|
|
48
|
+
/** Matches the selectors in @unovis/ts utils/theme.ts */
|
|
49
|
+
export function isDarkThemeActive(document) {
|
|
50
|
+
const html = document.documentElement;
|
|
51
|
+
const body = document.body;
|
|
52
|
+
return html?.getAttribute('data-theme') === 'dark' ||
|
|
53
|
+
html?.classList.contains('dark-theme') || html?.classList.contains('theme-dark') ||
|
|
54
|
+
body?.classList.contains('dark-theme') || body?.classList.contains('theme-dark');
|
|
55
|
+
}
|
|
56
|
+
/** Substitute var(--name[, fallback]) references in a CSS value using the active maps */
|
|
57
|
+
export function substituteVars(value, document, depth = 0) {
|
|
58
|
+
if (depth > 10 || !value.includes('var('))
|
|
59
|
+
return value;
|
|
60
|
+
const dark = isDarkThemeActive(document);
|
|
61
|
+
return value.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*|[^()]*\([^()]*\)[^()]*))?\)/g, (_, name, fallback) => {
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
63
|
+
const resolved = lookupVar(name, dark);
|
|
64
|
+
if (resolved !== undefined)
|
|
65
|
+
return substituteVars(resolved, document, depth + 1);
|
|
66
|
+
return fallback !== undefined ? substituteVars(fallback.trim(), document, depth + 1) : '';
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function lookupVar(name, dark) {
|
|
70
|
+
if (dark && varMaps.dark.has(name))
|
|
71
|
+
return varMaps.dark.get(name);
|
|
72
|
+
return varMaps.light.get(name);
|
|
73
|
+
}
|
|
74
|
+
/** Resolve a custom property for an element: inline chain first, then the theme maps */
|
|
75
|
+
export function resolveCssVar(name, el, document) {
|
|
76
|
+
let node = el;
|
|
77
|
+
while (node) {
|
|
78
|
+
const style = node.style;
|
|
79
|
+
const inline = style?.getPropertyValue(name);
|
|
80
|
+
if (inline)
|
|
81
|
+
return substituteVars(inline, document);
|
|
82
|
+
node = node.parentElement;
|
|
83
|
+
}
|
|
84
|
+
const value = lookupVar(name, isDarkThemeActive(document));
|
|
85
|
+
return value !== undefined ? substituteVars(value, document) : '';
|
|
86
|
+
}
|
|
87
|
+
function resolveFontProp(el, prop, cssValue, document) {
|
|
88
|
+
// Inline style on the element itself has the highest priority
|
|
89
|
+
const ownInline = el.style?.getPropertyValue(prop);
|
|
90
|
+
if (ownInline)
|
|
91
|
+
return substituteVars(ownInline, document).trim();
|
|
92
|
+
// A value from jsdom's own cascade wins next — resolve any var() refs in it
|
|
93
|
+
if (cssValue) {
|
|
94
|
+
const resolved = substituteVars(cssValue, document).trim();
|
|
95
|
+
if (resolved)
|
|
96
|
+
return resolved;
|
|
97
|
+
}
|
|
98
|
+
// Stylesheet rules matched manually (self, then ancestors — CSS inherits)
|
|
99
|
+
let node = el;
|
|
100
|
+
while (node) {
|
|
101
|
+
const matched = matchFontRules(node, prop);
|
|
102
|
+
if (matched)
|
|
103
|
+
return substituteVars(matched, document).trim();
|
|
104
|
+
node = node.parentElement;
|
|
105
|
+
}
|
|
106
|
+
// Then presentation attributes and inline styles up the ancestor chain
|
|
107
|
+
node = el;
|
|
108
|
+
while (node && node.namespaceURI?.endsWith('svg')) {
|
|
109
|
+
const attr = node.getAttribute(prop);
|
|
110
|
+
if (attr)
|
|
111
|
+
return substituteVars(attr, document).trim();
|
|
112
|
+
const inline = node.style?.getPropertyValue(prop);
|
|
113
|
+
if (inline)
|
|
114
|
+
return substituteVars(inline, document).trim();
|
|
115
|
+
node = node.parentElement;
|
|
116
|
+
}
|
|
117
|
+
switch (prop) {
|
|
118
|
+
case 'font-size': return FALLBACK_FONT_SIZE;
|
|
119
|
+
case 'font-family': return substituteVars(lookupVar('--vis-font-family', false) ?? FALLBACK_FONT_FAMILY, document);
|
|
120
|
+
default: return 'normal';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Resolve the CSS font shorthand used for canvas text measurement */
|
|
124
|
+
export function resolveFontShorthand(el, document) {
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
126
|
+
const style = tryNativeComputedStyle(el, document);
|
|
127
|
+
const fontSize = resolveFontProp(el, 'font-size', style?.fontSize ?? '', document);
|
|
128
|
+
const fontFamily = resolveFontProp(el, 'font-family', style?.fontFamily ?? '', document);
|
|
129
|
+
const fontWeight = resolveFontProp(el, 'font-weight', style?.fontWeight ?? '', document);
|
|
130
|
+
const fontStyle = resolveFontProp(el, 'font-style', style?.fontStyle ?? '', document);
|
|
131
|
+
const fontSizePx = parseFloat(fontSize) || 12;
|
|
132
|
+
return { font: `${fontStyle} ${fontWeight} ${fontSizePx}px ${fontFamily}`, fontSizePx };
|
|
133
|
+
}
|
|
134
|
+
let nativeGetComputedStyle;
|
|
135
|
+
function tryNativeComputedStyle(el, document) {
|
|
136
|
+
try {
|
|
137
|
+
return nativeGetComputedStyle?.(el);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export function installComputedStyle(window) {
|
|
144
|
+
const document = window.document;
|
|
145
|
+
nativeGetComputedStyle = window.getComputedStyle.bind(window);
|
|
146
|
+
// jsdom's computed style cascades lazily and its css parsers can throw on
|
|
147
|
+
// declarations they don't fully support (e.g. cssstyle's margin shorthand
|
|
148
|
+
// handling) — every native read must be treated as fallible.
|
|
149
|
+
const safe = (fn, fallback) => {
|
|
150
|
+
try {
|
|
151
|
+
return fn();
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return fallback;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const wrapped = (el, pseudo) => {
|
|
158
|
+
const target = safe(() => nativeGetComputedStyle(el, pseudo), undefined);
|
|
159
|
+
return new Proxy((target ?? {}), {
|
|
160
|
+
get(t, prop) {
|
|
161
|
+
if (prop === 'getPropertyValue') {
|
|
162
|
+
return (name) => {
|
|
163
|
+
if (name.startsWith('--'))
|
|
164
|
+
return resolveCssVar(name, el, document);
|
|
165
|
+
const value = safe(() => t.getPropertyValue?.(name) ?? '', '');
|
|
166
|
+
if (/^font/.test(name))
|
|
167
|
+
return resolveFontProp(el, name, value, document);
|
|
168
|
+
return value.includes('var(') ? substituteVars(value, document) : value;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (prop === 'fontSize')
|
|
172
|
+
return resolveFontProp(el, 'font-size', safe(() => t.fontSize, ''), document);
|
|
173
|
+
if (prop === 'fontFamily')
|
|
174
|
+
return resolveFontProp(el, 'font-family', safe(() => t.fontFamily, ''), document);
|
|
175
|
+
if (prop === 'fontWeight')
|
|
176
|
+
return resolveFontProp(el, 'font-weight', safe(() => t.fontWeight, ''), document);
|
|
177
|
+
if (prop === 'fontStyle')
|
|
178
|
+
return resolveFontProp(el, 'font-style', safe(() => t.fontStyle, ''), document);
|
|
179
|
+
const value = safe(() => Reflect.get(t, prop, t), undefined);
|
|
180
|
+
return typeof value === 'function' ? value.bind(t) : value;
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
window.getComputedStyle = wrapped;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=computed-style.js.map
|