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

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 (58) hide show
  1. package/dist/index.d.ts +1154 -119
  2. package/dist/index.js +533 -42
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +2 -1
  5. package/dist/styles.css.map +1 -0
  6. package/package.json +9 -3
  7. package/schema/chart.schema.json +8957 -0
  8. package/schema/table.schema.json +861 -0
  9. package/schema/vizspec.schema.json +17694 -0
  10. package/src/accessibility/alt-text.ts +39 -1
  11. package/src/accessibility/aria.ts +4 -2
  12. package/src/colors/__tests__/colorblind.test.ts +12 -0
  13. package/src/colors/colorblind.ts +11 -2
  14. package/src/colors/contrast.ts +11 -0
  15. package/src/colors/index.ts +9 -1
  16. package/src/colors/palettes.ts +47 -0
  17. package/src/helpers/__tests__/deep-merge.test.ts +54 -0
  18. package/src/helpers/__tests__/spec-builders.test.ts +3 -3
  19. package/src/helpers/deep-merge.ts +29 -0
  20. package/src/helpers/spec-builders.ts +7 -7
  21. package/src/index.ts +29 -1
  22. package/src/labels/collision.ts +1 -0
  23. package/src/layout/chrome.ts +24 -0
  24. package/src/layout/index.ts +1 -1
  25. package/src/locale/__tests__/format.test.ts +347 -11
  26. package/src/locale/format.ts +243 -18
  27. package/src/locale/index.ts +10 -1
  28. package/src/responsive/index.ts +2 -0
  29. package/src/responsive/metrics.ts +77 -10
  30. package/src/schema/__tests__/generated-artifacts.test.ts +42 -0
  31. package/src/schema/__tests__/schema.test.ts +179 -0
  32. package/src/styles/__tests__/reduced-motion-selectors.test.ts +28 -0
  33. package/src/styles/animation.css +197 -168
  34. package/src/styles/base.css +89 -62
  35. package/src/styles/chrome.css +173 -179
  36. package/src/styles/dark.css +38 -35
  37. package/src/styles/graph.css +116 -111
  38. package/src/styles/index.css +5 -0
  39. package/src/styles/keyframes.css +115 -100
  40. package/src/styles/legend.css +14 -12
  41. package/src/styles/reduced-motion.css +61 -40
  42. package/src/styles/series-search.css +153 -0
  43. package/src/styles/sparkline.css +14 -12
  44. package/src/styles/table-animation.css +82 -80
  45. package/src/styles/table.css +314 -302
  46. package/src/styles/token-definitions.ts +199 -0
  47. package/src/styles/tokens.css +132 -128
  48. package/src/styles/tooltip.css +109 -115
  49. package/src/styles/you-draw-it.css +126 -0
  50. package/src/types/__tests__/encoding.test.ts +86 -17
  51. package/src/types/__tests__/spec.test.ts +16 -12
  52. package/src/types/encoding.ts +69 -25
  53. package/src/types/events.ts +12 -0
  54. package/src/types/index.ts +39 -3
  55. package/src/types/layout.ts +414 -8
  56. package/src/types/schema-roots.ts +32 -0
  57. package/src/types/spec.ts +643 -84
  58. package/src/types/table.ts +3 -2
@@ -0,0 +1,199 @@
1
+ export interface CssTokenDef {
2
+ name: string;
3
+ light: string;
4
+ dark?: string;
5
+ comment?: string;
6
+ consumers?: string;
7
+ section?: string;
8
+ }
9
+
10
+ export const CSS_TOKEN_ROOT_SELECTORS: readonly string[] = [
11
+ '.oc-root',
12
+ '.oc-chart-root',
13
+ '.oc-table-wrapper',
14
+ '.oc-table-root',
15
+ '.oc-graph-wrapper',
16
+ '.oc-graph-root',
17
+ '.oc-sankey-root',
18
+ '.oc-tilemap-root',
19
+ '.oc-barlist-root',
20
+ '.oc-map-root',
21
+ ];
22
+
23
+ /**
24
+ * Text levels. Names invert across modes: in light mode "muted" sits at
25
+ * a lighter zinc step than "subtle" because both are picked relative to
26
+ * the active background, not from a fixed lightness ladder. The intent
27
+ * is "muted = first step away from primary text"; "subtle = next step
28
+ * down"; etc.
29
+ *
30
+ * Light mode: text=zinc-950, secondary=zinc-700, muted=zinc-500,
31
+ * subtle=zinc-400, faint=zinc-300
32
+ * Dark mode (dark.css): inverts the surface tokens but keeps the same
33
+ * muted -> subtle -> faint progression away from primary.
34
+ */
35
+
36
+ export const CSS_TOKENS: readonly CssTokenDef[] = [
37
+ {
38
+ name: '--oc-font-family',
39
+ light:
40
+ '\n "Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
41
+ },
42
+ {
43
+ name: '--oc-font-mono',
44
+ light: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
45
+ },
46
+
47
+ {
48
+ section: 'Animation easing presets via CSS linear()',
49
+ name: '--oc-ease-smooth',
50
+ light:
51
+ 'linear(\n 0,\n 0.157,\n 0.438,\n 0.64,\n 0.766,\n 0.85,\n 0.906,\n 0.941,\n 0.964,\n 0.978,\n 0.988,\n 0.994,\n 0.998,\n 1\n )',
52
+ },
53
+ {
54
+ name: '--oc-ease-snappy',
55
+ light:
56
+ 'linear(\n 0,\n 0.012,\n 0.048,\n 0.108,\n 0.194,\n 0.302,\n 0.426,\n 0.559,\n 0.69,\n 0.808,\n 0.905,\n 0.973,\n 1.013,\n 1.028,\n 1.023,\n 1.006,\n 0.984,\n 0.966,\n 0.957,\n 0.957,\n 0.964,\n 0.975,\n 0.986,\n 0.995,\n 1,\n 1.003,\n 1.002,\n 1,\n 0.998,\n 0.998,\n 0.999,\n 1\n )',
57
+ consumers: 'EASE_VAR_MAP in vanilla renderers',
58
+ },
59
+
60
+ { section: 'Animation timing defaults', name: '--oc-animation-duration', light: '500ms' },
61
+ { name: '--oc-animation-stagger', light: '80ms' },
62
+ { name: '--oc-annotation-delay', light: '200ms' },
63
+
64
+ { section: 'Typography scale (editorial design system)', name: '--oc-title-size', light: '26px' },
65
+ { name: '--oc-title-weight', light: '590' },
66
+ { name: '--oc-title-tracking', light: '-0.022em' },
67
+ { name: '--oc-subtitle-size', light: '14px' },
68
+ { name: '--oc-subtitle-weight', light: '450' },
69
+ { name: '--oc-source-size', light: '11px' },
70
+ { name: '--oc-source-weight', light: '450' },
71
+ { name: '--oc-body-size', light: '13px' },
72
+ { name: '--oc-eyebrow-size', light: '11px' },
73
+ { name: '--oc-eyebrow-weight', light: '550' },
74
+ { name: '--oc-eyebrow-tracking', light: '0.08em' },
75
+
76
+ { section: 'Surfaces (light mode defaults)', name: '--oc-bg', light: '#ffffff', dark: '#09090b' },
77
+ { name: '--oc-card', light: '#ffffff', dark: '#111113' },
78
+ {
79
+ name: '--oc-secondary',
80
+ light: '#f4f4f5',
81
+ dark: '#27272a',
82
+ comment: 'zinc-100, raised surface',
83
+ },
84
+
85
+ {
86
+ section: `Text levels. Names invert across modes: in light mode "muted" sits at
87
+ * a lighter zinc step than "subtle" because both are picked relative to
88
+ * the active background, not from a fixed lightness ladder. The intent
89
+ * is "muted = first step away from primary text"; "subtle = next step
90
+ * down"; etc.
91
+ *
92
+ * Light mode: text=zinc-950, secondary=zinc-700, muted=zinc-500,
93
+ * subtle=zinc-400, faint=zinc-300
94
+ * Dark mode (dark.css): inverts the surface tokens but keeps the same
95
+ * muted -> subtle -> faint progression away from primary.`,
96
+ name: '--oc-text',
97
+ light: '#09090b',
98
+ dark: '#f7f8f8',
99
+ },
100
+ { name: '--oc-text-secondary', light: '#3f3f46', dark: '#d0d6e0' },
101
+ { name: '--oc-text-muted', light: '#71717a', dark: '#a1a1aa' },
102
+ { name: '--oc-text-faint', light: '#d4d4d8', dark: '#52525b' },
103
+
104
+ {
105
+ section: 'Lines',
106
+ name: '--oc-gridline',
107
+ light: 'rgba(0, 0, 0, 0.1)',
108
+ dark: 'rgba(255, 255, 255, 0.05)',
109
+ },
110
+ {
111
+ name: '--oc-axis',
112
+ light: 'rgba(0, 0, 0, 0.1)',
113
+ dark: 'rgba(255, 255, 255, 0.1)',
114
+ },
115
+ {
116
+ name: '--oc-border',
117
+ light: 'rgba(0, 0, 0, 0.08)',
118
+ dark: 'rgba(255, 255, 255, 0.1)',
119
+ },
120
+ { name: '--oc-border-radius', light: '2px' },
121
+
122
+ {
123
+ section: 'Brand and semantic',
124
+ name: '--oc-accent',
125
+ light: '#06b6d4',
126
+ dark: '#06b6d4',
127
+ },
128
+ {
129
+ name: '--oc-accent-strong',
130
+ light: '#0891b2',
131
+ dark: '#06b6d4',
132
+ comment: 'darker cyan for line strokes on light bg',
133
+ consumers: 'stamped via adaptForLightLineStroke',
134
+ },
135
+ { name: '--oc-positive', light: '#10b981', dark: '#34d399' },
136
+ { name: '--oc-negative', light: '#e11d48', dark: '#fb7185' },
137
+ { name: '--oc-focus', light: '#3b82f6', dark: '#60a5fa' },
138
+ {
139
+ name: '--oc-focus-ring',
140
+ light: 'rgba(59, 130, 246, 0.1)',
141
+ comment: 'static, intentionally NOT derived from --oc-focus and NOT flipped in dark mode',
142
+ },
143
+ {
144
+ name: '--oc-focus-ring-strong',
145
+ light: 'rgba(59, 130, 246, 0.25)',
146
+ },
147
+ {
148
+ name: '--oc-editable-hover',
149
+ light: 'rgba(79, 70, 229, 0.35)',
150
+ comment: 'edit-mode hover outline, indigo one-off',
151
+ },
152
+
153
+ {
154
+ section: 'Spacing scale (4px base) — only --oc-space-2/4 are consumed (JS-stamped)',
155
+ name: '--oc-space-2',
156
+ light: '8px',
157
+ consumers:
158
+ 'stamped at mount from theme.spacing (theme-tokens.ts); component CSS must NOT consume these',
159
+ },
160
+ {
161
+ name: '--oc-space-4',
162
+ light: '16px',
163
+ consumers:
164
+ 'stamped at mount from theme.spacing (theme-tokens.ts); component CSS must NOT consume these',
165
+ },
166
+
167
+ {
168
+ section: 'Interactive states',
169
+ name: '--oc-hover-bg',
170
+ light: 'rgba(0, 0, 0, 0.025)',
171
+ dark: 'rgba(255, 255, 255, 0.05)',
172
+ },
173
+ {
174
+ name: '--oc-tooltip-bg',
175
+ light: 'rgba(255, 255, 255, 0.88)',
176
+ dark: 'rgba(17, 17, 19, 0.92)',
177
+ },
178
+ {
179
+ name: '--oc-tooltip-border',
180
+ light: 'rgba(0, 0, 0, 0.08)',
181
+ dark: 'rgba(255, 255, 255, 0.08)',
182
+ },
183
+ {
184
+ name: '--oc-tooltip-shadow',
185
+ light: '0 2px 8px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.12)',
186
+ dark: '0 2px 8px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.4)',
187
+ },
188
+ { name: '--oc-tooltip-text', light: '#09090b', dark: '#f7f8f8' },
189
+ { name: '--oc-legend-text', light: '#3f3f46', dark: '#d0d6e0' },
190
+ ];
191
+
192
+ const TOKEN_MAP = new Map(CSS_TOKENS.map((t) => [t.name, t]));
193
+
194
+ export function cssTokenDefault(name: string, mode: 'light' | 'dark'): string {
195
+ const token = TOKEN_MAP.get(name);
196
+ if (!token) throw new Error(`Unknown CSS token: ${name}`);
197
+ if (mode === 'dark' && token.dark !== undefined) return token.dark;
198
+ return token.light;
199
+ }
@@ -1,109 +1,113 @@
1
- /* ---------------------------------------------------------------------------
2
- * Custom properties (light mode defaults)
3
- *
4
- * These are FALLBACK defaults. At runtime, the JS theme engine stamps the
5
- * resolved theme as inline --oc-* custom properties on each .oc-root
6
- * container (see packages/vanilla/src/theme-tokens.ts). These CSS values
7
- * serve as fallbacks for contexts where JS hasn't mounted yet (SSR, static
8
- * HTML examples).
9
- * --------------------------------------------------------------------------- */
1
+ /* GENERATED FILE — do not edit. Source: packages/core/src/styles/token-definitions.ts. Regenerate: bun run generate:tokens */
10
2
 
11
- .oc-root,
12
- .oc-chart-root,
13
- .oc-table-wrapper,
14
- .oc-table-root,
15
- .oc-graph-wrapper,
16
- .oc-graph-root,
17
- .oc-sankey-root,
18
- .oc-tilemap-root,
19
- .oc-barlist-root {
20
- -webkit-font-smoothing: antialiased;
21
- -moz-osx-font-smoothing: grayscale;
3
+ @layer oc.tokens {
4
+ /* ---------------------------------------------------------------------------
5
+ * Custom properties (light mode defaults)
6
+ *
7
+ * These are FALLBACK defaults. At runtime, the JS theme engine stamps the
8
+ * resolved theme as inline --oc-* custom properties on each .oc-root
9
+ * container (see packages/vanilla/src/theme-tokens.ts). These CSS values
10
+ * serve as fallbacks for contexts where JS hasn't mounted yet (SSR, static
11
+ * HTML examples).
12
+ * --------------------------------------------------------------------------- */
13
+
14
+ .oc-root,
15
+ .oc-chart-root,
16
+ .oc-table-wrapper,
17
+ .oc-table-root,
18
+ .oc-graph-wrapper,
19
+ .oc-graph-root,
20
+ .oc-sankey-root,
21
+ .oc-tilemap-root,
22
+ .oc-barlist-root,
23
+ .oc-map-root {
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
22
26
 
23
- /* Opt out of iOS text auto-inflation so measured widths match rendered text. */
24
- -webkit-text-size-adjust: 100%;
25
- text-size-adjust: 100%;
27
+ /* Opt out of iOS text auto-inflation so measured widths match rendered text. */
28
+ -webkit-text-size-adjust: 100%;
29
+ text-size-adjust: 100%;
26
30
 
27
- --oc-font-family:
28
- "Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
29
- --oc-font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
31
+ --oc-font-family:
32
+ "Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
33
+ --oc-font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
30
34
 
31
- /* Animation easing presets via CSS linear() */
32
- --oc-ease-smooth: linear(
33
- 0,
34
- 0.157,
35
- 0.438,
36
- 0.64,
37
- 0.766,
38
- 0.85,
39
- 0.906,
40
- 0.941,
41
- 0.964,
42
- 0.978,
43
- 0.988,
44
- 0.994,
45
- 0.998,
46
- 1
47
- );
48
- --oc-ease-snappy: linear(
49
- 0,
50
- 0.012,
51
- 0.048,
52
- 0.108,
53
- 0.194,
54
- 0.302,
55
- 0.426,
56
- 0.559,
57
- 0.69,
58
- 0.808,
59
- 0.905,
60
- 0.973,
61
- 1.013,
62
- 1.028,
63
- 1.023,
64
- 1.006,
65
- 0.984,
66
- 0.966,
67
- 0.957,
68
- 0.957,
69
- 0.964,
70
- 0.975,
71
- 0.986,
72
- 0.995,
73
- 1,
74
- 1.003,
75
- 1.002,
76
- 1,
77
- 0.998,
78
- 0.998,
79
- 0.999,
80
- 1
81
- );
35
+ /* Animation easing presets via CSS linear() */
36
+ --oc-ease-smooth: linear(
37
+ 0,
38
+ 0.157,
39
+ 0.438,
40
+ 0.64,
41
+ 0.766,
42
+ 0.85,
43
+ 0.906,
44
+ 0.941,
45
+ 0.964,
46
+ 0.978,
47
+ 0.988,
48
+ 0.994,
49
+ 0.998,
50
+ 1
51
+ );
52
+ --oc-ease-snappy: linear(
53
+ 0,
54
+ 0.012,
55
+ 0.048,
56
+ 0.108,
57
+ 0.194,
58
+ 0.302,
59
+ 0.426,
60
+ 0.559,
61
+ 0.69,
62
+ 0.808,
63
+ 0.905,
64
+ 0.973,
65
+ 1.013,
66
+ 1.028,
67
+ 1.023,
68
+ 1.006,
69
+ 0.984,
70
+ 0.966,
71
+ 0.957,
72
+ 0.957,
73
+ 0.964,
74
+ 0.975,
75
+ 0.986,
76
+ 0.995,
77
+ 1,
78
+ 1.003,
79
+ 1.002,
80
+ 1,
81
+ 0.998,
82
+ 0.998,
83
+ 0.999,
84
+ 1
85
+ );
82
86
 
83
- /* Animation timing defaults */
84
- --oc-animation-duration: 500ms;
85
- --oc-animation-stagger: 80ms;
86
- --oc-annotation-delay: 200ms;
87
+ /* Animation timing defaults */
88
+ --oc-animation-duration: 500ms;
89
+ --oc-animation-stagger: 80ms;
90
+ --oc-annotation-delay: 200ms;
87
91
 
88
- /* Typography scale (editorial design system) */
89
- --oc-title-size: 26px;
90
- --oc-title-weight: 590;
91
- --oc-title-tracking: -0.022em;
92
- --oc-subtitle-size: 14px;
93
- --oc-subtitle-weight: 450;
94
- --oc-source-size: 11px;
95
- --oc-source-weight: 450;
96
- --oc-body-size: 13px;
97
- --oc-eyebrow-size: 11px;
98
- --oc-eyebrow-weight: 550;
99
- --oc-eyebrow-tracking: 0.08em;
92
+ /* Typography scale (editorial design system) */
93
+ --oc-title-size: 26px;
94
+ --oc-title-weight: 590;
95
+ --oc-title-tracking: -0.022em;
96
+ --oc-subtitle-size: 14px;
97
+ --oc-subtitle-weight: 450;
98
+ --oc-source-size: 11px;
99
+ --oc-source-weight: 450;
100
+ --oc-body-size: 13px;
101
+ --oc-eyebrow-size: 11px;
102
+ --oc-eyebrow-weight: 550;
103
+ --oc-eyebrow-tracking: 0.08em;
100
104
 
101
- /* Surfaces (light mode defaults) */
102
- --oc-bg: #ffffff;
103
- --oc-card: #ffffff;
104
- --oc-secondary: #f4f4f5; /* zinc-100, raised surface */
105
+ /* Surfaces (light mode defaults) */
106
+ --oc-bg: #ffffff;
107
+ --oc-card: #ffffff;
108
+ --oc-secondary: #f4f4f5; /* zinc-100, raised surface */
105
109
 
106
- /*
110
+ /*
107
111
  * Text levels. Names invert across modes: in light mode "muted" sits at
108
112
  * a lighter zinc step than "subtle" because both are picked relative to
109
113
  * the active background, not from a fixed lightness ladder. The intent
@@ -115,38 +119,38 @@
115
119
  * Dark mode (dark.css): inverts the surface tokens but keeps the same
116
120
  * muted -> subtle -> faint progression away from primary.
117
121
  */
118
- --oc-text: #09090b;
119
- --oc-text-secondary: #3f3f46;
120
- --oc-text-muted: #71717a;
121
- --oc-text-subtle: #a1a1aa;
122
- --oc-text-faint: #d4d4d8;
122
+ --oc-text: #09090b;
123
+ --oc-text-secondary: #3f3f46;
124
+ --oc-text-muted: #71717a;
125
+ --oc-text-faint: #d4d4d8;
123
126
 
124
- /* Lines */
125
- --oc-gridline: rgba(0, 0, 0, 0.1);
126
- --oc-axis: rgba(0, 0, 0, 0.1);
127
- --oc-border: rgba(0, 0, 0, 0.08);
128
- --oc-border-radius: 2px;
127
+ /* Lines */
128
+ --oc-gridline: rgba(0, 0, 0, 0.1);
129
+ --oc-axis: rgba(0, 0, 0, 0.1);
130
+ --oc-border: rgba(0, 0, 0, 0.08);
131
+ --oc-border-radius: 2px;
129
132
 
130
- /* Brand and semantic */
131
- --oc-accent: #06b6d4;
132
- --oc-accent-strong: #0891b2; /* darker cyan for line strokes on light bg */
133
- --oc-positive: #10b981;
134
- --oc-negative: #e11d48;
135
- --oc-focus: #3b82f6;
133
+ /* Brand and semantic */
134
+ --oc-accent: #06b6d4;
135
+ --oc-accent-strong: #0891b2; /* darker cyan for line strokes on light bg */
136
+ --oc-positive: #10b981;
137
+ --oc-negative: #e11d48;
138
+ --oc-focus: #3b82f6;
139
+ /* static, intentionally NOT derived from --oc-focus and NOT flipped in dark mode */
140
+ --oc-focus-ring: rgba(59, 130, 246, 0.1);
141
+ --oc-focus-ring-strong: rgba(59, 130, 246, 0.25);
142
+ --oc-editable-hover: rgba(79, 70, 229, 0.35); /* edit-mode hover outline, indigo one-off */
136
143
 
137
- /* Spacing scale (4px base) */
138
- --oc-space-1: 4px;
139
- --oc-space-2: 8px;
140
- --oc-space-3: 12px;
141
- --oc-space-4: 16px;
142
- --oc-space-6: 24px;
143
- --oc-space-8: 32px;
144
+ /* Spacing scale (4px base) — only --oc-space-2/4 are consumed (JS-stamped) */
145
+ --oc-space-2: 8px;
146
+ --oc-space-4: 16px;
144
147
 
145
- /* Interactive states */
146
- --oc-hover-bg: rgba(0, 0, 0, 0.025);
147
- --oc-tooltip-bg: rgba(255, 255, 255, 0.88);
148
- --oc-tooltip-border: rgba(0, 0, 0, 0.08);
149
- --oc-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.12);
150
- --oc-tooltip-text: #09090b;
151
- --oc-legend-text: #3f3f46;
148
+ /* Interactive states */
149
+ --oc-hover-bg: rgba(0, 0, 0, 0.025);
150
+ --oc-tooltip-bg: rgba(255, 255, 255, 0.88);
151
+ --oc-tooltip-border: rgba(0, 0, 0, 0.08);
152
+ --oc-tooltip-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.12);
153
+ --oc-tooltip-text: #09090b;
154
+ --oc-legend-text: #3f3f46;
155
+ }
152
156
  }