@opendata-ai/openchart-core 8.0.0-rc.2 → 8.0.0-rc.21

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.
Files changed (82) hide show
  1. package/dist/index.d.ts +490 -25
  2. package/dist/index.js +57 -19
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/dist/styles.css.map +1 -1
  6. package/package.json +1 -2
  7. package/schema/chart.schema.json +485 -35
  8. package/schema/table.schema.json +35 -1
  9. package/schema/vizspec.schema.json +1305 -120
  10. package/src/accessibility/__tests__/alt-text.test.ts +0 -110
  11. package/src/accessibility/__tests__/aria.test.ts +0 -125
  12. package/src/accessibility/alt-text.ts +0 -155
  13. package/src/accessibility/aria.ts +0 -75
  14. package/src/accessibility/index.ts +0 -6
  15. package/src/colors/__tests__/colorblind.test.ts +0 -75
  16. package/src/colors/__tests__/contrast.test.ts +0 -119
  17. package/src/colors/__tests__/palettes.test.ts +0 -74
  18. package/src/colors/colorblind.ts +0 -131
  19. package/src/colors/contrast.ts +0 -143
  20. package/src/colors/index.ts +0 -36
  21. package/src/colors/palettes.ts +0 -206
  22. package/src/helpers/__tests__/deep-merge.test.ts +0 -54
  23. package/src/helpers/__tests__/spec-builders.test.ts +0 -338
  24. package/src/helpers/deep-merge.ts +0 -29
  25. package/src/helpers/spec-builders.ts +0 -479
  26. package/src/index.ts +0 -215
  27. package/src/labels/__tests__/collision.test.ts +0 -310
  28. package/src/labels/collision.ts +0 -249
  29. package/src/labels/index.ts +0 -13
  30. package/src/layout/__tests__/chrome.test.ts +0 -208
  31. package/src/layout/__tests__/text-measure.test.ts +0 -57
  32. package/src/layout/chrome.ts +0 -470
  33. package/src/layout/index.ts +0 -18
  34. package/src/layout/text-measure.ts +0 -404
  35. package/src/layout/text-wrap.ts +0 -93
  36. package/src/locale/__tests__/format.test.ts +0 -606
  37. package/src/locale/format.ts +0 -459
  38. package/src/locale/index.ts +0 -21
  39. package/src/responsive/__tests__/breakpoints.test.ts +0 -146
  40. package/src/responsive/__tests__/metrics.test.ts +0 -52
  41. package/src/responsive/breakpoints.ts +0 -172
  42. package/src/responsive/index.ts +0 -53
  43. package/src/responsive/metrics.ts +0 -299
  44. package/src/schema/__tests__/generated-artifacts.test.ts +0 -42
  45. package/src/schema/__tests__/schema.test.ts +0 -179
  46. package/src/styles/__tests__/reduced-motion-selectors.test.ts +0 -28
  47. package/src/styles/animation.css +0 -199
  48. package/src/styles/base.css +0 -102
  49. package/src/styles/chrome.css +0 -174
  50. package/src/styles/dark.css +0 -44
  51. package/src/styles/graph.css +0 -135
  52. package/src/styles/index.css +0 -28
  53. package/src/styles/keyframes.css +0 -135
  54. package/src/styles/legend.css +0 -18
  55. package/src/styles/reduced-motion.css +0 -67
  56. package/src/styles/series-search.css +0 -153
  57. package/src/styles/sparkline.css +0 -16
  58. package/src/styles/table-animation.css +0 -93
  59. package/src/styles/table.css +0 -398
  60. package/src/styles/token-definitions.ts +0 -199
  61. package/src/styles/tokens.css +0 -156
  62. package/src/styles/tooltip.css +0 -126
  63. package/src/styles/you-draw-it.css +0 -126
  64. package/src/theme/__tests__/dark-mode.test.ts +0 -232
  65. package/src/theme/__tests__/defaults.test.ts +0 -73
  66. package/src/theme/__tests__/resolve.test.ts +0 -277
  67. package/src/theme/dark-mode.ts +0 -258
  68. package/src/theme/defaults.ts +0 -110
  69. package/src/theme/index.ts +0 -8
  70. package/src/theme/presets.ts +0 -64
  71. package/src/theme/resolve.ts +0 -312
  72. package/src/types/__tests__/encoding.test.ts +0 -368
  73. package/src/types/__tests__/gradient.test.ts +0 -95
  74. package/src/types/__tests__/spec.test.ts +0 -499
  75. package/src/types/encoding.ts +0 -280
  76. package/src/types/events.ts +0 -154
  77. package/src/types/index.ts +0 -287
  78. package/src/types/layout.ts +0 -1966
  79. package/src/types/schema-roots.ts +0 -32
  80. package/src/types/spec.ts +0 -2867
  81. package/src/types/table.ts +0 -108
  82. package/src/types/theme.ts +0 -209
@@ -1,208 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { resolveTheme } from '../../theme/resolve';
3
- import type { Chrome } from '../../types/spec';
4
- import { computeChrome } from '../chrome';
5
- import { BRAND_FONT_SIZE, estimateTextHeight } from '../text-measure';
6
-
7
- const theme = resolveTheme();
8
-
9
- describe('computeChrome', () => {
10
- it('returns zero heights when chrome is undefined', () => {
11
- const result = computeChrome(undefined, theme, 600);
12
- expect(result.topHeight).toBe(0);
13
- expect(result.bottomHeight).toBe(0);
14
- expect(result.title).toBeUndefined();
15
- expect(result.subtitle).toBeUndefined();
16
- });
17
-
18
- it('reserves brand height when chrome is empty but chart is wide enough', () => {
19
- const result = computeChrome({}, theme, 600);
20
- const pad = theme.spacing.padding;
21
- const expectedBottom =
22
- theme.spacing.chartToFooter + estimateTextHeight(BRAND_FONT_SIZE, 1) + pad;
23
- expect(result.topHeight).toBe(0);
24
- expect(result.bottomHeight).toBe(expectedBottom);
25
- });
26
-
27
- it('returns zero bottom height when chrome is empty and chart is too narrow for brand', () => {
28
- const result = computeChrome({}, theme, 100);
29
- expect(result.topHeight).toBe(0);
30
- expect(result.bottomHeight).toBe(0);
31
- });
32
-
33
- it('reserves brand height in compact mode for wide charts', () => {
34
- const chrome: Chrome = { title: 'Title', source: 'Source' };
35
- const result = computeChrome(chrome, theme, 600, undefined, 'compact');
36
- const pad = theme.spacing.padding;
37
- const expectedBottom =
38
- theme.spacing.chartToFooter + estimateTextHeight(BRAND_FONT_SIZE, 1) + pad;
39
- expect(result.topHeight).toBeGreaterThan(0);
40
- expect(result.source).toBeUndefined(); // compact hides bottom chrome text
41
- expect(result.bottomHeight).toBe(expectedBottom);
42
- });
43
-
44
- it('returns zero bottom height in compact mode for narrow charts', () => {
45
- const chrome: Chrome = { title: 'Title', source: 'Source' };
46
- const result = computeChrome(chrome, theme, 100, undefined, 'compact');
47
- expect(result.bottomHeight).toBe(0);
48
- });
49
-
50
- it('positions title correctly', () => {
51
- const chrome: Chrome = { title: 'GDP Growth Rate' };
52
- const result = computeChrome(chrome, theme, 600);
53
-
54
- expect(result.title).toBeDefined();
55
- expect(result.title!.text).toBe('GDP Growth Rate');
56
- expect(result.title!.x).toBe(theme.spacing.padding);
57
- expect(result.title!.y).toBe(theme.spacing.padding);
58
- expect(result.title!.style.fontSize).toBe(theme.chrome.title.fontSize);
59
- expect(result.title!.style.fontWeight).toBe(theme.chrome.title.fontWeight);
60
- expect(result.topHeight).toBeGreaterThan(0);
61
- });
62
-
63
- it('positions subtitle below title', () => {
64
- const chrome: Chrome = { title: 'Title', subtitle: 'Subtitle text' };
65
- const result = computeChrome(chrome, theme, 600);
66
-
67
- expect(result.title).toBeDefined();
68
- expect(result.subtitle).toBeDefined();
69
- expect(result.subtitle!.y).toBeGreaterThan(result.title!.y);
70
- });
71
-
72
- it('computes top height from title + subtitle + gaps', () => {
73
- const chrome: Chrome = { title: 'Title', subtitle: 'Subtitle' };
74
- const result = computeChrome(chrome, theme, 600);
75
-
76
- // Top height should account for title, gap, subtitle, and chromeToChart
77
- expect(result.topHeight).toBeGreaterThan(30);
78
- });
79
-
80
- it('positions source in bottom chrome', () => {
81
- const chrome: Chrome = { source: 'Source: World Bank' };
82
- const result = computeChrome(chrome, theme, 600);
83
-
84
- expect(result.source).toBeDefined();
85
- expect(result.source!.text).toBe('Source: World Bank');
86
- expect(result.bottomHeight).toBeGreaterThan(0);
87
- });
88
-
89
- it('handles ChromeText objects with style overrides', () => {
90
- const chrome: Chrome = {
91
- title: {
92
- text: 'Custom Title',
93
- style: { fontSize: 24, fontWeight: 700, color: '#ff0000' },
94
- },
95
- };
96
- const result = computeChrome(chrome, theme, 600);
97
-
98
- expect(result.title!.style.fontSize).toBe(24);
99
- expect(result.title!.style.fontWeight).toBe(700);
100
- expect(result.title!.style.fill).toBe('#ff0000');
101
- });
102
-
103
- it('sets maxWidth based on width minus padding', () => {
104
- const chrome: Chrome = { title: 'Title' };
105
- const result = computeChrome(chrome, theme, 600);
106
-
107
- const expectedMaxWidth = 600 - theme.spacing.padding * 2 - 5;
108
- expect(result.title!.maxWidth).toBe(expectedMaxWidth);
109
- });
110
-
111
- it('handles full chrome with all elements', () => {
112
- const chrome: Chrome = {
113
- title: 'Title',
114
- subtitle: 'Subtitle',
115
- source: 'Source',
116
- byline: 'By Author',
117
- footer: 'Footer note',
118
- };
119
- const result = computeChrome(chrome, theme, 600);
120
-
121
- expect(result.title).toBeDefined();
122
- expect(result.subtitle).toBeDefined();
123
- expect(result.source).toBeDefined();
124
- expect(result.byline).toBeDefined();
125
- expect(result.footer).toBeDefined();
126
- expect(result.topHeight).toBeGreaterThan(0);
127
- expect(result.bottomHeight).toBeGreaterThan(0);
128
- });
129
-
130
- it('reserves extra height when title wraps to multiple lines at narrow widths', () => {
131
- const longTitle =
132
- 'Global Economic Recovery Trends Show Surprising Resilience Across Major Markets';
133
- const chrome: Chrome = { title: longTitle, subtitle: 'Subtitle text' };
134
-
135
- // At wide width, title fits on one line
136
- const wide = computeChrome(chrome, theme, 800);
137
- // At narrow width, title wraps to multiple lines
138
- const narrow = computeChrome(chrome, theme, 300);
139
-
140
- // Narrow should reserve more top height due to title wrapping
141
- expect(narrow.topHeight).toBeGreaterThan(wide.topHeight);
142
-
143
- // Subtitle should be pushed further down to avoid collision
144
- expect(narrow.subtitle!.y).toBeGreaterThan(wide.subtitle!.y);
145
- });
146
-
147
- it('reserves extra height when subtitle contains newline characters', () => {
148
- const chrome: Chrome = { title: 'Title', subtitle: 'Line one\nLine two' };
149
- const withNewline = computeChrome(chrome, theme, 600);
150
-
151
- const chromeNoNewline: Chrome = { title: 'Title', subtitle: 'Line one Line two' };
152
- const withoutNewline = computeChrome(chromeNoNewline, theme, 600);
153
-
154
- // The newline version should reserve more top height since it forces two lines
155
- expect(withNewline.topHeight).toBeGreaterThan(withoutNewline.topHeight);
156
- });
157
-
158
- it('handles consecutive newlines in chrome text', () => {
159
- const chrome: Chrome = { title: 'Title', subtitle: 'Above\n\nBelow' };
160
- const result = computeChrome(chrome, theme, 600);
161
-
162
- // Three segments: "Above", "", "Below" -> 3 lines total
163
- // This should be taller than a simple two-line subtitle
164
- const twoLine: Chrome = { title: 'Title', subtitle: 'Above\nBelow' };
165
- const twoLineResult = computeChrome(twoLine, theme, 600);
166
-
167
- expect(result.topHeight).toBeGreaterThan(twoLineResult.topHeight);
168
- });
169
-
170
- it('handles newlines combined with long text that also word-wraps', () => {
171
- const longSegment = 'This is a very long segment that should wrap at narrow widths on its own';
172
- const chrome: Chrome = { title: 'Title', subtitle: `${longSegment}\nShort` };
173
- const result = computeChrome(chrome, theme, 300);
174
-
175
- // At narrow width, the long segment wraps AND the \n adds another line
176
- const noNewline: Chrome = { title: 'Title', subtitle: longSegment };
177
- const noNewlineResult = computeChrome(noNewline, theme, 300);
178
-
179
- expect(result.topHeight).toBeGreaterThan(noNewlineResult.topHeight);
180
- });
181
-
182
- it('uses measureText function when provided', () => {
183
- const measureText = (text: string, fontSize: number) => ({
184
- width: text.length * fontSize * 0.6,
185
- height: fontSize * 1.2,
186
- });
187
-
188
- const chrome: Chrome = { title: 'Title' };
189
- const result = computeChrome(chrome, theme, 600, measureText);
190
- expect(result.title).toBeDefined();
191
- // Just verify it runs without error; exact values depend on measure fn
192
- });
193
-
194
- it('returns zero bottom height when watermark is false and no bottom chrome', () => {
195
- const chrome: Chrome = { title: 'Title' };
196
- const result = computeChrome(chrome, theme, 600, undefined, 'full', undefined, false);
197
- expect(result.bottomHeight).toBe(0);
198
- });
199
-
200
- it('does not reserve brand width for bottom text when watermark is false', () => {
201
- const chrome: Chrome = { source: 'Source: World Bank' };
202
- const withWatermark = computeChrome(chrome, theme, 600, undefined, 'full', undefined, true);
203
- const withoutWatermark = computeChrome(chrome, theme, 600, undefined, 'full', undefined, false);
204
-
205
- // Without watermark, source text gets full width (not reduced by BRAND_RESERVE_WIDTH)
206
- expect(withoutWatermark.source!.maxWidth).toBeGreaterThan(withWatermark.source!.maxWidth);
207
- });
208
- });
@@ -1,57 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { estimateTextHeight, estimateTextWidth } from '../text-measure';
3
-
4
- describe('estimateTextWidth', () => {
5
- it('returns 0 for empty string', () => {
6
- expect(estimateTextWidth('', 14)).toBe(0);
7
- });
8
-
9
- it('scales linearly with text length', () => {
10
- const w5 = estimateTextWidth('abcde', 14);
11
- const w10 = estimateTextWidth('abcdeabcde', 14);
12
- expect(w10).toBeCloseTo(w5 * 2, 1);
13
- });
14
-
15
- it('scales with font size', () => {
16
- const w14 = estimateTextWidth('test', 14);
17
- const w28 = estimateTextWidth('test', 28);
18
- expect(w28).toBeCloseTo(w14 * 2, 1);
19
- });
20
-
21
- it('heavier weights produce wider estimates', () => {
22
- const normal = estimateTextWidth('test', 14, 400);
23
- const bold = estimateTextWidth('test', 14, 700);
24
- expect(bold).toBeGreaterThan(normal);
25
- });
26
-
27
- it('produces reasonable values for common cases', () => {
28
- // "GDP Growth Rate" at 18px should be roughly 120-180px wide
29
- const width = estimateTextWidth('GDP Growth Rate', 18);
30
- expect(width).toBeGreaterThan(100);
31
- expect(width).toBeLessThan(250);
32
- });
33
-
34
- // Pins the per-character width table output. Any change to the tables
35
- // shifts layout-wide text wrapping decisions, so we freeze the exact value.
36
- it('estimateTextWidth("sample", 14, 400) returns the locked numeric value', () => {
37
- // Per-char lookup: s(0.5) + a(0.556) + m(0.833) + p(0.556) + l(0.222) + e(0.556) = 3.223
38
- // 3.223 * 14 * 1.0 = 45.122
39
- expect(estimateTextWidth('sample', 14, 400)).toBeCloseTo(45.122, 2);
40
- });
41
- });
42
-
43
- describe('estimateTextHeight', () => {
44
- it('single line at 14px with 1.3 lineHeight is 18.2', () => {
45
- expect(estimateTextHeight(14, 1, 1.3)).toBeCloseTo(18.2, 1);
46
- });
47
-
48
- it('two lines are double the height', () => {
49
- const h1 = estimateTextHeight(14, 1);
50
- const h2 = estimateTextHeight(14, 2);
51
- expect(h2).toBeCloseTo(h1 * 2, 1);
52
- });
53
-
54
- it('defaults to 1 line and 1.3 lineHeight', () => {
55
- expect(estimateTextHeight(10)).toBeCloseTo(13, 1);
56
- });
57
- });