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

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 (59) hide show
  1. package/dist/index.d.ts +1348 -132
  2. package/dist/index.js +556 -48
  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 +9086 -0
  8. package/schema/table.schema.json +861 -0
  9. package/schema/vizspec.schema.json +17922 -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/breakpoints.ts +7 -2
  29. package/src/responsive/index.ts +2 -0
  30. package/src/responsive/metrics.ts +77 -10
  31. package/src/schema/__tests__/generated-artifacts.test.ts +42 -0
  32. package/src/schema/__tests__/schema.test.ts +179 -0
  33. package/src/styles/__tests__/reduced-motion-selectors.test.ts +28 -0
  34. package/src/styles/animation.css +211 -168
  35. package/src/styles/base.css +89 -62
  36. package/src/styles/chrome.css +173 -179
  37. package/src/styles/dark.css +38 -35
  38. package/src/styles/graph.css +116 -111
  39. package/src/styles/index.css +5 -0
  40. package/src/styles/keyframes.css +130 -99
  41. package/src/styles/legend.css +14 -12
  42. package/src/styles/reduced-motion.css +61 -40
  43. package/src/styles/series-search.css +153 -0
  44. package/src/styles/sparkline.css +14 -12
  45. package/src/styles/table-animation.css +82 -80
  46. package/src/styles/table.css +314 -302
  47. package/src/styles/token-definitions.ts +199 -0
  48. package/src/styles/tokens.css +132 -128
  49. package/src/styles/tooltip.css +109 -115
  50. package/src/styles/you-draw-it.css +126 -0
  51. package/src/types/__tests__/encoding.test.ts +86 -17
  52. package/src/types/__tests__/spec.test.ts +16 -12
  53. package/src/types/encoding.ts +69 -25
  54. package/src/types/events.ts +107 -13
  55. package/src/types/index.ts +42 -3
  56. package/src/types/layout.ts +484 -11
  57. package/src/types/schema-roots.ts +32 -0
  58. package/src/types/spec.ts +718 -87
  59. package/src/types/table.ts +3 -2
@@ -1,132 +1,126 @@
1
- /* ---------------------------------------------------------------------------
2
- * Tooltip
3
- * Editorial card matching the new design system: zinc surface, cyan accent,
4
- * Inter Variable, mono numerics. Sized for a slice tooltip that lists every
5
- * series at the snapped x.
6
- * --------------------------------------------------------------------------- */
1
+ @layer oc.components {
2
+ /* ---------------------------------------------------------------------------
3
+ * Tooltip
4
+ * Editorial card matching the new design system: zinc surface, cyan accent,
5
+ * Inter Variable, mono numerics. Sized for a slice tooltip that lists every
6
+ * series at the snapped x.
7
+ * --------------------------------------------------------------------------- */
7
8
 
8
- .oc-tooltip {
9
- position: absolute;
10
- display: none;
11
- pointer-events: none;
12
- z-index: 1000;
13
- background: var(--oc-tooltip-bg);
14
- backdrop-filter: blur(14px);
15
- border: 1px solid var(--oc-tooltip-border);
16
- border-radius: var(--oc-border-radius, 4px);
17
- box-shadow: var(--oc-tooltip-shadow);
18
- color: var(--oc-tooltip-text);
19
- font-family: var(--oc-font-family);
20
- font-size: 12px;
21
- padding: 0;
22
- max-width: 280px;
23
- min-width: 160px;
24
- line-height: 1.4;
25
- animation: oc-tooltip-in 120ms ease-out;
26
- transition:
27
- left 100ms ease-in-out,
28
- top 100ms ease-in-out;
9
+ .oc-tooltip {
10
+ position: absolute;
11
+ display: none;
12
+ pointer-events: none;
13
+ z-index: 1000;
14
+ background: var(--oc-tooltip-bg);
15
+ backdrop-filter: blur(14px);
16
+ border: 1px solid var(--oc-tooltip-border);
17
+ border-radius: var(--oc-border-radius, 2px);
18
+ box-shadow: var(--oc-tooltip-shadow);
19
+ color: var(--oc-tooltip-text);
20
+ font-family: var(--oc-font-family);
21
+ font-size: 12px;
22
+ padding: 0;
23
+ max-width: 280px;
24
+ min-width: 160px;
25
+ line-height: 1.4;
26
+ animation: oc-tooltip-in 120ms ease-out;
27
+ transition:
28
+ left 100ms ease-in-out,
29
+ top 100ms ease-in-out;
29
30
 
30
- & .oc-tooltip-header {
31
- display: flex;
32
- align-items: center;
33
- gap: 8px;
34
- padding: 8px 12px 6px;
35
- }
31
+ & .oc-tooltip-header {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 8px;
35
+ padding: 8px 12px 6px;
36
+ }
36
37
 
37
- & .oc-tooltip-dot {
38
- width: 8px;
39
- height: 8px;
40
- border-radius: 50%;
41
- flex-shrink: 0;
42
- }
38
+ & .oc-tooltip-dot {
39
+ width: 8px;
40
+ height: 8px;
41
+ border-radius: 50%;
42
+ flex-shrink: 0;
43
+ }
43
44
 
44
- & .oc-tooltip-title {
45
- font-weight: 590;
46
- font-size: 11px;
47
- letter-spacing: 0.04em;
48
- text-transform: uppercase;
49
- color: var(--oc-text-muted);
50
- white-space: nowrap;
51
- overflow: hidden;
52
- text-overflow: ellipsis;
53
- }
45
+ & .oc-tooltip-title {
46
+ font-weight: 590;
47
+ font-size: 11px;
48
+ letter-spacing: 0.04em;
49
+ text-transform: uppercase;
50
+ color: var(--oc-text-muted);
51
+ white-space: nowrap;
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ }
54
55
 
55
- & .oc-tooltip-body {
56
- padding: 6px 12px 10px;
57
- border-top: 1px solid var(--oc-tooltip-border);
58
- }
56
+ & .oc-tooltip-body {
57
+ padding: 6px 12px 10px;
58
+ border-top: 1px solid var(--oc-tooltip-border);
59
+ }
59
60
 
60
- /* No separator when body is the only child */
61
- & .oc-tooltip-body:first-child {
62
- border-top: none;
63
- padding-top: 10px;
64
- }
61
+ /* No separator when body is the only child */
62
+ & .oc-tooltip-body:first-child {
63
+ border-top: none;
64
+ padding-top: 10px;
65
+ }
65
66
 
66
- & .oc-tooltip-row {
67
- display: flex;
68
- align-items: baseline;
69
- justify-content: space-between;
70
- gap: 16px;
71
- padding: 2px 0;
72
- }
67
+ & .oc-tooltip-row {
68
+ display: flex;
69
+ align-items: baseline;
70
+ justify-content: space-between;
71
+ gap: 16px;
72
+ padding: 2px 0;
73
+ }
73
74
 
74
- & .oc-tooltip-row-swatch {
75
- display: inline-block;
76
- width: 8px;
77
- height: 8px;
78
- border-radius: 50%;
79
- margin-right: 6px;
80
- flex-shrink: 0;
81
- transform: translateY(-1px);
82
- }
75
+ & .oc-tooltip-row-swatch {
76
+ display: inline-block;
77
+ width: 8px;
78
+ height: 8px;
79
+ border-radius: 50%;
80
+ margin-right: 6px;
81
+ flex-shrink: 0;
82
+ transform: translateY(-1px);
83
+ }
83
84
 
84
- & .oc-tooltip-label {
85
- color: var(--oc-text-secondary);
86
- font-size: 12px;
87
- font-weight: 400;
88
- white-space: nowrap;
89
- flex-shrink: 0;
90
- display: inline-flex;
91
- align-items: center;
92
- }
85
+ & .oc-tooltip-label {
86
+ color: var(--oc-text-secondary);
87
+ font-size: 12px;
88
+ font-weight: 400;
89
+ white-space: nowrap;
90
+ flex-shrink: 0;
91
+ display: inline-flex;
92
+ align-items: center;
93
+ }
93
94
 
94
- & .oc-tooltip-value {
95
- font-weight: 510;
96
- font-size: 12px;
97
- font-variant-numeric: tabular-nums;
98
- color: var(--oc-tooltip-text);
99
- text-align: right;
100
- overflow: hidden;
101
- text-overflow: ellipsis;
102
- white-space: nowrap;
95
+ & .oc-tooltip-value {
96
+ font-weight: 510;
97
+ font-size: 12px;
98
+ font-variant-numeric: tabular-nums;
99
+ color: var(--oc-tooltip-text);
100
+ text-align: right;
101
+ overflow: hidden;
102
+ text-overflow: ellipsis;
103
+ white-space: nowrap;
104
+ }
103
105
  }
104
- }
105
106
 
106
- /* ---------------------------------------------------------------------------
107
- * Crosshair guideline + per-series snap dots (line/area charts)
108
- * Animate the snap between adjacent points so the indicator slides instead
109
- * of jumping. ~50ms ease-in-out keeps the motion crisp without lagging.
110
- * --------------------------------------------------------------------------- */
107
+ /* ---------------------------------------------------------------------------
108
+ * Crosshair guideline + per-series snap dots (line/area charts)
109
+ * Animate the snap between adjacent points so the indicator slides instead
110
+ * of jumping. ~50ms ease-in-out keeps the motion crisp without lagging.
111
+ * --------------------------------------------------------------------------- */
111
112
 
112
- .oc-crosshair {
113
- pointer-events: none;
114
- transition:
115
- x1 50ms ease-in-out,
116
- x2 50ms ease-in-out;
117
- }
118
-
119
- .oc-snap-dots circle {
120
- pointer-events: none;
121
- transition:
122
- cx 50ms ease-in-out,
123
- cy 50ms ease-in-out;
124
- }
113
+ .oc-crosshair {
114
+ pointer-events: none;
115
+ transition:
116
+ x1 50ms ease-in-out,
117
+ x2 50ms ease-in-out;
118
+ }
125
119
 
126
- @media (prefers-reduced-motion: reduce) {
127
- .oc-tooltip,
128
- .oc-crosshair,
129
120
  .oc-snap-dots circle {
130
- transition: none;
121
+ pointer-events: none;
122
+ transition:
123
+ cx 50ms ease-in-out,
124
+ cy 50ms ease-in-out;
131
125
  }
132
126
  }
@@ -0,0 +1,126 @@
1
+ @layer oc.components {
2
+ /* ---------------------------------------------------------------------------
3
+ * You draw it (youDrawIt)
4
+ *
5
+ * NYT-style draw-then-reveal. SVG-space elements (hatched region, guess path,
6
+ * reveal clip) render inside the chart SVG; the prompt + skip-to-reveal button
7
+ * are an absolutely-positioned HTML overlay the vanilla adapter sizes over the
8
+ * drawing region (inline left/top/width/height). All colors come from --oc-*
9
+ * tokens so they flip in .oc-dark.
10
+ * --------------------------------------------------------------------------- */
11
+
12
+ /* Hatched "draw here" region. Fill comes from an SVG <pattern> (line color =
13
+ * the target series color); this only governs overall opacity + fade-out. */
14
+ .oc-ydi-region {
15
+ opacity: 0.35;
16
+ transition: opacity 0.4s ease;
17
+ }
18
+
19
+ .oc-ydi-region[data-ydi-hidden="true"] {
20
+ opacity: 0;
21
+ }
22
+
23
+ /* The vertical rule at `from` where drawing begins. */
24
+ .oc-ydi-boundary {
25
+ stroke: var(--oc-text-muted);
26
+ stroke-width: 1;
27
+ stroke-dasharray: 3 3;
28
+ stroke-opacity: 0.6;
29
+ }
30
+
31
+ /* Reader's guess: a distinct pen-like stroke that reads as "yours" against the
32
+ * solid real line. Dashed + focus-blue so it stays legible in both modes. */
33
+ .oc-ydi-guess {
34
+ stroke: var(--oc-focus);
35
+ stroke-width: 2.5;
36
+ stroke-linecap: round;
37
+ stroke-linejoin: round;
38
+ stroke-dasharray: 1 6;
39
+ }
40
+
41
+ /* Host-supplied comparison line ("what everyone else guessed"). Muted so it
42
+ * sits behind the reader's guess and the real line. */
43
+ .oc-ydi-comparison {
44
+ stroke: var(--oc-text-muted);
45
+ stroke-width: 1.5;
46
+ stroke-opacity: 0.5;
47
+ stroke-dasharray: 4 4;
48
+ }
49
+
50
+ /* Reveal clip rect: the width animates from the `from` boundary to the full
51
+ * area on reveal, wiping the real line in left-to-right. The vanilla adapter
52
+ * adds .oc-ydi-clip-animate right before setting the final width; reduced
53
+ * motion skips the class so the width snaps. */
54
+ .oc-ydi-clip-rect.oc-ydi-clip-animate {
55
+ transition: width 0.7s cubic-bezier(0.22, 1, 0.36, 1);
56
+ }
57
+
58
+ /* HTML controls overlay: prompt (top) + reveal button (bottom-right). */
59
+ .oc-ydi-controls {
60
+ display: flex;
61
+ flex-direction: column;
62
+ justify-content: space-between;
63
+ align-items: flex-end;
64
+ pointer-events: none;
65
+ font-family: var(--oc-font-family);
66
+ padding: 8px;
67
+ box-sizing: border-box;
68
+ z-index: 10;
69
+ }
70
+
71
+ .oc-ydi-prompt {
72
+ align-self: center;
73
+ max-width: 100%;
74
+ padding: 6px 12px;
75
+ border-radius: 6px;
76
+ background: var(--oc-card);
77
+ border: 1px solid var(--oc-border);
78
+ color: var(--oc-text-secondary);
79
+ font-size: 13px;
80
+ font-weight: 500;
81
+ line-height: 16px;
82
+ text-align: center;
83
+ box-shadow: var(--oc-tooltip-shadow);
84
+ }
85
+
86
+ .oc-ydi-controls.oc-ydi-revealed .oc-ydi-prompt {
87
+ display: none;
88
+ }
89
+
90
+ /* Skip-to-reveal button. pointer-events re-enabled (the overlay disables it)
91
+ * so it stays clickable and keyboard-reachable. Min height enforced inline by
92
+ * the adapter (>= 24px effective touch target). */
93
+ .oc-ydi-reveal-button {
94
+ pointer-events: auto;
95
+ display: inline-flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ padding: 6px 14px;
99
+ border: 1px solid var(--oc-border);
100
+ border-radius: 6px;
101
+ background: var(--oc-bg);
102
+ color: var(--oc-text);
103
+ font-family: inherit;
104
+ font-size: 13px;
105
+ font-weight: 500;
106
+ cursor: pointer;
107
+ box-shadow: var(--oc-tooltip-shadow);
108
+ transition:
109
+ background-color 0.15s,
110
+ border-color 0.15s;
111
+ }
112
+
113
+ .oc-ydi-reveal-button:hover {
114
+ background: var(--oc-hover-bg);
115
+ }
116
+
117
+ .oc-ydi-reveal-button:focus-visible {
118
+ outline: 2px solid var(--oc-focus);
119
+ outline-offset: 2px;
120
+ }
121
+
122
+ .oc-ydi-reveal-button:disabled {
123
+ opacity: 0.5;
124
+ cursor: default;
125
+ }
126
+ }
@@ -27,7 +27,7 @@ function getOptionalChannels(markType: MarkType): string[] {
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
29
  describe('MARK_ENCODING_RULES', () => {
30
- it('has entries for all 11 mark types', () => {
30
+ it('has entries for all 16 mark types', () => {
31
31
  const expectedTypes: MarkType[] = [
32
32
  'bar',
33
33
  'line',
@@ -40,6 +40,11 @@ describe('MARK_ENCODING_RULES', () => {
40
40
  'tick',
41
41
  'rect',
42
42
  'lollipop',
43
+ 'beeswarm',
44
+ 'range',
45
+ 'waffle',
46
+ 'calendar',
47
+ 'parliament',
43
48
  ];
44
49
  for (const type of expectedTypes) {
45
50
  expect(MARK_ENCODING_RULES[type]).toBeDefined();
@@ -67,21 +72,21 @@ describe('bar encoding rules', () => {
67
72
  expect(getOptionalChannels('bar')).toContain('y2');
68
73
  });
69
74
 
70
- it('supports opacity, tooltip, href, order, detail', () => {
75
+ it('supports opacity, tooltip, detail', () => {
71
76
  const optionals = getOptionalChannels('bar');
72
77
  expect(optionals).toContain('opacity');
73
78
  expect(optionals).toContain('tooltip');
74
- expect(optionals).toContain('href');
75
- expect(optionals).toContain('order');
76
79
  expect(optionals).toContain('detail');
77
80
  });
78
81
 
79
- it('does not support shape, strokeDash, text, theta, radius', () => {
82
+ it('does not support shape, strokeDash, text, theta, href, order, radius', () => {
80
83
  const channels = getChannelNames('bar');
81
84
  expect(channels).not.toContain('shape');
82
85
  expect(channels).not.toContain('strokeDash');
83
86
  expect(channels).not.toContain('text');
84
87
  expect(channels).not.toContain('theta');
88
+ expect(channels).not.toContain('href');
89
+ expect(channels).not.toContain('order');
85
90
  expect(channels).not.toContain('radius');
86
91
  });
87
92
  });
@@ -108,6 +113,12 @@ describe('line encoding rules', () => {
108
113
  expect(channels).not.toContain('x2');
109
114
  expect(channels).not.toContain('y2');
110
115
  });
116
+
117
+ it('does not support href or order (removed dead channels)', () => {
118
+ const channels = getChannelNames('line');
119
+ expect(channels).not.toContain('href');
120
+ expect(channels).not.toContain('order');
121
+ });
111
122
  });
112
123
 
113
124
  describe('point encoding rules', () => {
@@ -122,8 +133,8 @@ describe('point encoding rules', () => {
122
133
  }
123
134
  });
124
135
 
125
- it('supports shape as optional', () => {
126
- expect(getOptionalChannels('point')).toContain('shape');
136
+ it('does not support shape (removed dead channel)', () => {
137
+ expect(getChannelNames('point')).not.toContain('shape');
127
138
  });
128
139
 
129
140
  it('supports size as optional quantitative', () => {
@@ -134,18 +145,18 @@ describe('point encoding rules', () => {
134
145
  });
135
146
 
136
147
  describe('arc encoding rules', () => {
137
- it('requires y (quantitative) and color (nominal/ordinal)', () => {
148
+ it('requires theta (quantitative) and color (nominal/ordinal)', () => {
138
149
  const rules = MARK_ENCODING_RULES.arc;
139
- expect(rules.y.required).toBe(true);
140
- expect(rules.y.allowedTypes).toContain('quantitative');
150
+ expect(rules.theta.required).toBe(true);
151
+ expect(rules.theta.allowedTypes).toContain('quantitative');
141
152
  expect(rules.color.required).toBe(true);
142
153
  expect(rules.color.allowedTypes).toContain('nominal');
143
154
  });
144
155
 
145
- it('supports theta and radius as optional', () => {
156
+ it('supports y as optional deprecated alias (radius was removed)', () => {
146
157
  const optionals = getOptionalChannels('arc');
147
- expect(optionals).toContain('theta');
148
- expect(optionals).toContain('radius');
158
+ expect(optionals).toContain('y');
159
+ expect(getChannelNames('arc')).not.toContain('radius');
149
160
  });
150
161
  });
151
162
 
@@ -188,11 +199,10 @@ describe('tick encoding rules', () => {
188
199
  expect(getRequiredChannels('tick')).toContain('y');
189
200
  });
190
201
 
191
- it('supports opacity, tooltip, href, detail', () => {
202
+ it('supports opacity, tooltip, detail', () => {
192
203
  const optionals = getOptionalChannels('tick');
193
204
  expect(optionals).toContain('opacity');
194
205
  expect(optionals).toContain('tooltip');
195
- expect(optionals).toContain('href');
196
206
  expect(optionals).toContain('detail');
197
207
  });
198
208
  });
@@ -203,12 +213,11 @@ describe('rect encoding rules', () => {
203
213
  expect(getRequiredChannels('rect')).toContain('y');
204
214
  });
205
215
 
206
- it('supports x2, y2, opacity, order', () => {
216
+ it('supports x2, y2, opacity', () => {
207
217
  const optionals = getOptionalChannels('rect');
208
218
  expect(optionals).toContain('x2');
209
219
  expect(optionals).toContain('y2');
210
220
  expect(optionals).toContain('opacity');
211
- expect(optionals).toContain('order');
212
221
  });
213
222
  });
214
223
 
@@ -229,6 +238,51 @@ describe('lollipop encoding rules', () => {
229
238
  });
230
239
  });
231
240
 
241
+ describe('beeswarm encoding rules', () => {
242
+ it('marks both axes optional (the compiler enforces the combination)', () => {
243
+ const rules = MARK_ENCODING_RULES.beeswarm;
244
+ expect(rules.x.required).toBe(false);
245
+ expect(rules.y.required).toBe(false);
246
+ });
247
+
248
+ it('accepts quantitative and nominal/ordinal on both axes', () => {
249
+ const rules = MARK_ENCODING_RULES.beeswarm;
250
+ for (const axis of [rules.x, rules.y]) {
251
+ expect(axis.allowedTypes).toContain('quantitative');
252
+ expect(axis.allowedTypes).toContain('nominal');
253
+ expect(axis.allowedTypes).toContain('ordinal');
254
+ }
255
+ });
256
+
257
+ it('supports optional size (quantitative) for sized dots', () => {
258
+ const rules = MARK_ENCODING_RULES.beeswarm;
259
+ expect(rules.size.required).toBe(false);
260
+ expect(rules.size.allowedTypes).toEqual(['quantitative']);
261
+ });
262
+ });
263
+
264
+ describe('waffle encoding rules', () => {
265
+ it('requires a quantitative y (the share value) and a categorical color', () => {
266
+ const rules = MARK_ENCODING_RULES.waffle;
267
+ expect(rules.y.required).toBe(true);
268
+ expect(rules.y.allowedTypes).toEqual(['quantitative']);
269
+ expect(rules.color.required).toBe(true);
270
+ expect(rules.color.allowedTypes).toContain('nominal');
271
+ expect(rules.color.allowedTypes).toContain('ordinal');
272
+ });
273
+
274
+ it('accepts theta as an optional quantitative channel (arc parity)', () => {
275
+ const rules = MARK_ENCODING_RULES.waffle;
276
+ expect(rules.theta?.required).toBe(false);
277
+ expect(rules.theta?.allowedTypes).toEqual(['quantitative']);
278
+ });
279
+
280
+ it('does not require positional x (no axes)', () => {
281
+ const rules = MARK_ENCODING_RULES.waffle;
282
+ expect(rules.x.required).toBe(false);
283
+ });
284
+ });
285
+
232
286
  describe('common channels across marks', () => {
233
287
  it('tooltip is optional on all mark types', () => {
234
288
  const allTypes: MarkType[] = [
@@ -243,6 +297,11 @@ describe('common channels across marks', () => {
243
297
  'tick',
244
298
  'rect',
245
299
  'lollipop',
300
+ 'beeswarm',
301
+ 'range',
302
+ 'waffle',
303
+ 'calendar',
304
+ 'parliament',
246
305
  ];
247
306
  for (const type of allTypes) {
248
307
  const rules = MARK_ENCODING_RULES[type];
@@ -265,6 +324,11 @@ describe('common channels across marks', () => {
265
324
  'tick',
266
325
  'rect',
267
326
  'lollipop',
327
+ 'beeswarm',
328
+ 'range',
329
+ 'waffle',
330
+ 'calendar',
331
+ 'parliament',
268
332
  ];
269
333
  for (const type of allTypes) {
270
334
  const rules = MARK_ENCODING_RULES[type];
@@ -287,6 +351,11 @@ describe('common channels across marks', () => {
287
351
  'tick',
288
352
  'rect',
289
353
  'lollipop',
354
+ 'beeswarm',
355
+ 'range',
356
+ 'waffle',
357
+ 'calendar',
358
+ 'parliament',
290
359
  ];
291
360
  for (const type of allTypes) {
292
361
  const rules = MARK_ENCODING_RULES[type];
@@ -8,7 +8,6 @@ import type {
8
8
  VizSpec,
9
9
  } from '../spec';
10
10
  import {
11
- CHART_TYPES,
12
11
  isChartSpec,
13
12
  isGraphSpec,
14
13
  isRangeAnnotation,
@@ -139,8 +138,8 @@ describe('type guard mutual exclusivity', () => {
139
138
  // ---------------------------------------------------------------------------
140
139
 
141
140
  describe('MARK_TYPES', () => {
142
- it('contains all 11 mark types', () => {
143
- expect(MARK_TYPES.size).toBe(11);
141
+ it('contains all 16 mark types', () => {
142
+ expect(MARK_TYPES.size).toBe(16);
144
143
  });
145
144
 
146
145
  it('contains expected types', () => {
@@ -156,6 +155,11 @@ describe('MARK_TYPES', () => {
156
155
  'tick',
157
156
  'rect',
158
157
  'lollipop',
158
+ 'beeswarm',
159
+ 'range',
160
+ 'waffle',
161
+ 'calendar',
162
+ 'parliament',
159
163
  ];
160
164
  for (const t of expected) {
161
165
  expect(MARK_TYPES.has(t)).toBe(true);
@@ -167,10 +171,6 @@ describe('MARK_TYPES', () => {
167
171
  expect(MARK_TYPES.has('graph')).toBe(false);
168
172
  expect(MARK_TYPES.has('map')).toBe(false);
169
173
  });
170
-
171
- it('CHART_TYPES is an alias for MARK_TYPES', () => {
172
- expect(CHART_TYPES).toBe(MARK_TYPES);
173
- });
174
174
  });
175
175
 
176
176
  // ---------------------------------------------------------------------------
@@ -424,13 +424,13 @@ describe('type-level spec construction', () => {
424
424
 
425
425
  describe('type-level rejection', () => {
426
426
  it('compiles with @ts-expect-error annotations intact (runtime no-op)', () => {
427
- // Arc requires y + color. Missing color must error.
427
+ // Arc requires theta + color. Missing color must error.
428
428
  const _arcMissingColor: ChartSpec = {
429
429
  mark: 'arc',
430
430
  data: [],
431
431
  // @ts-expect-error ArcEncoding requires color channel
432
432
  encoding: {
433
- y: { field: 'value', type: 'quantitative' },
433
+ theta: { field: 'value', type: 'quantitative' },
434
434
  },
435
435
  };
436
436
 
@@ -464,10 +464,14 @@ describe('type-level rejection', () => {
464
464
  },
465
465
  };
466
466
 
467
- // Arc without theta must compile (theta is optional per MARK_ENCODING_RULES).
468
- const _arcNoTheta: ChartSpec = {
467
+ // Arc with only the deprecated y alias (no theta) must error: ArcEncoding
468
+ // requires theta, the canonical value channel in v8. y is still accepted
469
+ // at runtime (spec-sugar expands it into theta with a warning), but the
470
+ // type no longer allows authoring it directly.
471
+ const _arcDeprecatedYOnly: ChartSpec = {
469
472
  mark: 'arc',
470
473
  data: [],
474
+ // @ts-expect-error ArcEncoding requires theta; y alone is not enough
471
475
  encoding: {
472
476
  y: { field: 'value', type: 'quantitative' },
473
477
  color: { field: 'category', type: 'nominal' },
@@ -488,7 +492,7 @@ describe('type-level rejection', () => {
488
492
  void _textMissingText;
489
493
  void _typoField;
490
494
  void _validTyped;
491
- void _arcNoTheta;
495
+ void _arcDeprecatedYOnly;
492
496
  void _untypedSpec;
493
497
  expect(true).toBe(true);
494
498
  });