@opendata-ai/openchart-core 7.10.0 → 8.0.0-rc.1

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 +530 -41
  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 +74 -9
  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,28 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { CSS_TOKEN_ROOT_SELECTORS } from '../token-definitions';
5
+
6
+ describe('reduced-motion.css', () => {
7
+ it('uses the same root selectors as CSS_TOKEN_ROOT_SELECTORS', () => {
8
+ const css = readFileSync(resolve(__dirname, '../reduced-motion.css'), 'utf8');
9
+ const selectors = CSS_TOKEN_ROOT_SELECTORS.every((sel) => css.includes(sel));
10
+ expect(selectors).toBe(true);
11
+
12
+ const isBlocks = [...css.matchAll(/:is\(\s*([\s\S]*?)\s*\)/g)];
13
+ const selectorsInCss = new Set(
14
+ isBlocks.flatMap((m) =>
15
+ m[1]
16
+ .split(',')
17
+ .map((s) => s.trim())
18
+ .filter(Boolean),
19
+ ),
20
+ );
21
+ for (const sel of CSS_TOKEN_ROOT_SELECTORS) {
22
+ expect(selectorsInCss.has(sel)).toBe(true);
23
+ }
24
+ for (const sel of selectorsInCss) {
25
+ expect(CSS_TOKEN_ROOT_SELECTORS).toContain(sel);
26
+ }
27
+ });
28
+ });
@@ -1,170 +1,199 @@
1
- /* ---------------------------------------------------------------------------
2
- * Animation scoped rules (.oc-animate enables animation on the chart root)
3
- * --------------------------------------------------------------------------- */
4
-
5
- .oc-animate {
6
- /* Vertical bars (default): smooth ease-out, no overshoot.
7
- oc-mark-bar is reserved for future mark type aliases. */
8
- & .oc-mark-rect rect,
9
- & .oc-mark-bar rect {
10
- animation: oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;
11
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
1
+ @layer oc.animation {
2
+ /* ---------------------------------------------------------------------------
3
+ * Animation scoped rules (.oc-animate enables animation on the chart root)
4
+ * --------------------------------------------------------------------------- */
5
+
6
+ .oc-animate {
7
+ /* Vertical bars (default): smooth ease-out, no overshoot.
8
+ oc-mark-bar is reserved for future mark type aliases. */
9
+ & .oc-mark-rect rect,
10
+ & .oc-mark-bar rect {
11
+ animation: oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;
12
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
13
+ }
14
+
15
+ /* Horizontal bars (data-orient is on the mark group, not the SVG root) */
16
+ & .oc-mark-rect[data-orient="horizontal"] rect,
17
+ & .oc-mark-bar[data-orient="horizontal"] rect {
18
+ animation-name: oc-enter-bar-h;
19
+ }
20
+
21
+ /* Stacked bar/column segments: chain sequentially so each segment starts
22
+ right when the previous one finishes, creating one fluid reveal.
23
+ Uses linear easing so handoffs between segments are seamless (no
24
+ deceleration/acceleration stutter at segment boundaries). */
25
+ & .oc-mark-rect[data-stack-pos] rect {
26
+ animation-duration: var(--oc-stack-segment-duration, 150ms);
27
+ animation-timing-function: linear;
28
+ animation-delay: calc(
29
+ var(--oc-animation-stagger) *
30
+ var(--oc-mark-index, 0) +
31
+ var(--oc-stack-pos, 0) *
32
+ var(--oc-stack-segment-duration, 150ms)
33
+ );
34
+ }
35
+
36
+ /* Line marks: entire group clips left-to-right (no WAAPI needed) */
37
+ & .oc-mark-line {
38
+ animation: oc-enter-line var(--oc-animation-duration)
39
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
40
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
41
+ }
42
+
43
+ /* Area marks: entire group clips left-to-right + fades in */
44
+ & .oc-mark-area {
45
+ animation: oc-enter-line var(--oc-animation-duration)
46
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
47
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
48
+ }
49
+
50
+ /* Arc marks (pie/donut slices): simple fade in.
51
+ Scale transforms break arc positioning because arcs use translate()
52
+ on parent groups. A clean fade is more elegant for pie/donut anyway. */
53
+ & .oc-mark-arc {
54
+ animation: oc-enter-fade-only var(--oc-animation-duration)
55
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
56
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
57
+ }
58
+
59
+ /* Point marks (scatter, dot).
60
+ Points render as bare <circle> elements with class oc-mark-point directly,
61
+ not wrapped in a group, so we target circle.oc-mark-point (not descendant).
62
+ Duration is 40% of the configured duration (quick pop-in relative to other marks). */
63
+ & circle.oc-mark-point,
64
+ & circle.oc-mark-circle {
65
+ animation: oc-enter-fade-only calc(var(--oc-animation-duration) * 0.4)
66
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
67
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
68
+ }
69
+
70
+ /* Points on line/area charts: delay so they pop in as the line draws through. */
71
+ & .oc-mark-line ~ circle.oc-mark-point,
72
+ & .oc-mark-area ~ circle.oc-mark-point {
73
+ animation-delay: calc(
74
+ var(--oc-animation-duration) *
75
+ 0.35 +
76
+ var(--oc-animation-stagger) *
77
+ var(--oc-mark-index, 0)
78
+ );
79
+ }
80
+
81
+ /* Text marks: fade + slight slide up */
82
+ & .oc-mark-text text {
83
+ animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.6)
84
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
85
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
86
+ }
87
+
88
+ /* Rule marks: fade in */
89
+ & .oc-mark-rule line {
90
+ animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.5)
91
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
92
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
93
+ }
94
+
95
+ /* Tick marks: fade in */
96
+ & .oc-mark-tick line {
97
+ animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.5)
98
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
99
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
100
+ }
101
+
102
+ /* Data labels: fade after their parent mark */
103
+ & .oc-mark-label {
104
+ animation: oc-enter-fade 300ms var(--oc-ease-smooth) both;
105
+ animation-delay: calc(
106
+ var(--oc-animation-stagger) *
107
+ var(--oc-mark-index, 0) +
108
+ var(--oc-animation-duration) *
109
+ 0.7
110
+ );
111
+ }
112
+
113
+ /* Annotations: fade in after marks finish */
114
+ & .oc-annotation {
115
+ animation: oc-enter-fade 400ms var(--oc-ease-smooth) both;
116
+ animation-delay: calc(var(--oc-animation-duration) + var(--oc-annotation-delay, 200ms));
117
+ }
118
+
119
+ /* Tilemap tiles: fade in with jittered stagger for organic feel.
120
+ Per-tile delay is computed in the renderer with pseudo-random variation. */
121
+ & .oc-tilemap-tile {
122
+ animation: oc-enter-fade-only calc(var(--oc-animation-duration) * 0.35) ease-in both;
123
+ animation-delay: var(--oc-tile-delay, 0ms);
124
+ }
125
+
126
+ /* Map features: fill color reveal with shuffled stagger.
127
+ Per-feature --oc-map-delay is computed in the renderer via a seeded
128
+ Fisher-Yates shuffle so features pop in organically. */
129
+ & .oc-map-feature {
130
+ animation: oc-enter-map-fill var(--oc-animation-duration)
131
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
132
+ animation-delay: var(--oc-map-delay, 0ms);
133
+ }
134
+
135
+ /* Bulk mode: high feature counts (counties, ~3k+) get a single group
136
+ fade instead of per-feature fill animations. opacity is GPU-compositable
137
+ so this stays smooth regardless of child count. */
138
+ & .oc-map-features[data-bulk-animate] {
139
+ animation: oc-enter-fade-only var(--oc-animation-duration)
140
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
141
+ }
142
+
143
+ & .oc-map-features[data-bulk-animate] .oc-map-feature {
144
+ animation: none;
145
+ }
146
+
147
+ /* Map borders: fade in near the end of the feature sweep */
148
+ & .oc-map-borders path {
149
+ animation: oc-enter-fade-only calc(var(--oc-animation-duration) * 0.4) ease-in both;
150
+ animation-delay: calc(var(--oc-animation-duration) * 0.7);
151
+ }
152
+
153
+ /* Sankey nodes: fade in with stagger by column depth (left-to-right) */
154
+ & .oc-sankey-node rect {
155
+ animation: oc-enter-fade-only var(--oc-animation-duration)
156
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
157
+ animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
158
+ }
159
+
160
+ /* Sankey links: fade in after nodes, with stagger.
161
+ Links delay by 30% of duration so nodes appear first, then links flow in. */
162
+ & .oc-sankey-link path {
163
+ animation: oc-enter-fade-only var(--oc-animation-duration)
164
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
165
+ animation-delay: calc(
166
+ var(--oc-animation-duration) *
167
+ 0.3 +
168
+ var(--oc-animation-stagger) *
169
+ var(--oc-mark-index, 0)
170
+ );
171
+ }
172
+
173
+ /* Barlist rows: fade + slide up per row, bars grow left-to-right */
174
+ & .oc-barlist-row {
175
+ animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.6)
176
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
177
+ animation-delay: var(--oc-row-delay, 0ms);
178
+ }
179
+
180
+ & .oc-barlist-bar {
181
+ animation: oc-enter-bar-h var(--oc-animation-duration)
182
+ var(--oc-animation-ease, var(--oc-ease-smooth)) both;
183
+ animation-delay: var(--oc-row-delay, 0ms);
184
+ }
185
+ }
186
+
187
+ /* ---------------------------------------------------------------------------
188
+ * Sparkline mode: pair the line/area reveal with a stronger ease-out so it
189
+ * doesn't read as a uniform left-to-right swipe. cubic-bezier(0.16, 1, 0.3, 1)
190
+ * is "expo-out" — fast initial draw that decelerates noticeably at the end,
191
+ * giving the trend a hand-drawn feel. Duration is bumped via the engine
192
+ * (compile.ts) so the cleanup timer stays in sync.
193
+ * --------------------------------------------------------------------------- */
194
+
195
+ .oc-animate[data-display="sparkline"] .oc-mark-line,
196
+ .oc-animate[data-display="sparkline"] .oc-mark-area {
197
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
12
198
  }
13
-
14
- /* Horizontal bars (data-orient is on the mark group, not the SVG root) */
15
- & .oc-mark-rect[data-orient="horizontal"] rect,
16
- & .oc-mark-bar[data-orient="horizontal"] rect {
17
- animation-name: oc-enter-bar-h;
18
- }
19
-
20
- /* Stacked bar/column segments: chain sequentially so each segment starts
21
- right when the previous one finishes, creating one fluid reveal.
22
- Uses linear easing so handoffs between segments are seamless (no
23
- deceleration/acceleration stutter at segment boundaries). */
24
- & .oc-mark-rect[data-stack-pos] rect {
25
- animation-duration: var(--oc-stack-segment-duration, 150ms);
26
- animation-timing-function: linear;
27
- animation-delay: calc(
28
- var(--oc-animation-stagger) *
29
- var(--oc-mark-index, 0) +
30
- var(--oc-stack-pos, 0) *
31
- var(--oc-stack-segment-duration, 150ms)
32
- );
33
- }
34
-
35
- /* Line marks: entire group clips left-to-right (no WAAPI needed) */
36
- & .oc-mark-line {
37
- animation: oc-enter-line var(--oc-animation-duration)
38
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
39
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
40
- }
41
-
42
- /* Area marks: entire group clips left-to-right + fades in */
43
- & .oc-mark-area {
44
- animation: oc-enter-line var(--oc-animation-duration)
45
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
46
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
47
- }
48
-
49
- /* Arc marks (pie/donut slices): simple fade in.
50
- Scale transforms break arc positioning because arcs use translate()
51
- on parent groups. A clean fade is more elegant for pie/donut anyway. */
52
- & .oc-mark-arc {
53
- animation: oc-enter-fade-only var(--oc-animation-duration)
54
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
55
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
56
- }
57
-
58
- /* Point marks (scatter, dot).
59
- Points render as bare <circle> elements with class oc-mark-point directly,
60
- not wrapped in a group, so we target circle.oc-mark-point (not descendant).
61
- Duration is 40% of the configured duration (quick pop-in relative to other marks). */
62
- & circle.oc-mark-point,
63
- & circle.oc-mark-circle {
64
- animation: oc-enter-fade-only calc(var(--oc-animation-duration) * 0.4)
65
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
66
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
67
- }
68
-
69
- /* Points on line/area charts: delay so they pop in as the line draws through. */
70
- & .oc-mark-line ~ circle.oc-mark-point,
71
- & .oc-mark-area ~ circle.oc-mark-point {
72
- animation-delay: calc(
73
- var(--oc-animation-duration) *
74
- 0.35 +
75
- var(--oc-animation-stagger) *
76
- var(--oc-mark-index, 0)
77
- );
78
- }
79
-
80
- /* Text marks: fade + slight slide up */
81
- & .oc-mark-text text {
82
- animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.6)
83
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
84
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
85
- }
86
-
87
- /* Rule marks: fade in */
88
- & .oc-mark-rule line {
89
- animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.5)
90
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
91
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
92
- }
93
-
94
- /* Tick marks: fade in */
95
- & .oc-mark-tick line {
96
- animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.5)
97
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
98
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
99
- }
100
-
101
- /* Data labels: fade after their parent mark */
102
- & .oc-mark-label {
103
- animation: oc-enter-fade 300ms var(--oc-ease-smooth) both;
104
- animation-delay: calc(
105
- var(--oc-animation-stagger) *
106
- var(--oc-mark-index, 0) +
107
- var(--oc-animation-duration) *
108
- 0.7
109
- );
110
- }
111
-
112
- /* Annotations: fade in after marks finish */
113
- & .oc-annotation {
114
- animation: oc-enter-fade 400ms var(--oc-ease-smooth) both;
115
- animation-delay: calc(var(--oc-animation-duration) + var(--oc-annotation-delay, 200ms));
116
- }
117
-
118
- /* Tilemap tiles: fade in with jittered stagger for organic feel.
119
- Per-tile delay is computed in the renderer with pseudo-random variation. */
120
- & .oc-tilemap-tile {
121
- animation: oc-enter-fade-only calc(var(--oc-animation-duration) * 0.35) ease-in both;
122
- animation-delay: var(--oc-tile-delay, 0ms);
123
- }
124
-
125
- /* Sankey nodes: fade in with stagger by column depth (left-to-right) */
126
- & .oc-sankey-node rect {
127
- animation: oc-enter-fade-only var(--oc-animation-duration)
128
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
129
- animation-delay: calc(var(--oc-animation-stagger) * var(--oc-mark-index, 0));
130
- }
131
-
132
- /* Sankey links: fade in after nodes, with stagger.
133
- Links delay by 30% of duration so nodes appear first, then links flow in. */
134
- & .oc-sankey-link path {
135
- animation: oc-enter-fade-only var(--oc-animation-duration)
136
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
137
- animation-delay: calc(
138
- var(--oc-animation-duration) *
139
- 0.3 +
140
- var(--oc-animation-stagger) *
141
- var(--oc-mark-index, 0)
142
- );
143
- }
144
-
145
- /* Barlist rows: fade + slide up per row, bars grow left-to-right */
146
- & .oc-barlist-row {
147
- animation: oc-enter-fade calc(var(--oc-animation-duration) * 0.6)
148
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
149
- animation-delay: var(--oc-row-delay, 0ms);
150
- }
151
-
152
- & .oc-barlist-bar {
153
- animation: oc-enter-bar-h var(--oc-animation-duration)
154
- var(--oc-animation-ease, var(--oc-ease-smooth)) both;
155
- animation-delay: var(--oc-row-delay, 0ms);
156
- }
157
- }
158
-
159
- /* ---------------------------------------------------------------------------
160
- * Sparkline mode: pair the line/area reveal with a stronger ease-out so it
161
- * doesn't read as a uniform left-to-right swipe. cubic-bezier(0.16, 1, 0.3, 1)
162
- * is "expo-out" — fast initial draw that decelerates noticeably at the end,
163
- * giving the trend a hand-drawn feel. Duration is bumped via the engine
164
- * (compile.ts) so the cleanup timer stays in sync.
165
- * --------------------------------------------------------------------------- */
166
-
167
- .oc-animate[data-display="sparkline"] .oc-mark-line,
168
- .oc-animate[data-display="sparkline"] .oc-mark-area {
169
- animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
170
199
  }
@@ -1,75 +1,102 @@
1
- /* ---------------------------------------------------------------------------
2
- * Wrapper roots
3
- * --------------------------------------------------------------------------- */
1
+ @layer oc.base {
2
+ /* ---------------------------------------------------------------------------
3
+ * Wrapper roots
4
+ * --------------------------------------------------------------------------- */
4
5
 
5
- .oc-chart-root {
6
- width: 100%;
7
- height: 100%;
8
- }
6
+ .oc-chart-root {
7
+ width: 100%;
8
+ height: 100%;
9
+ }
9
10
 
10
- .oc-table-root,
11
- .oc-graph-root,
12
- .oc-sankey-root,
13
- .oc-barlist-root {
14
- width: 100%;
15
- height: 100%;
16
- }
11
+ .oc-table-root,
12
+ .oc-graph-root,
13
+ .oc-sankey-root,
14
+ .oc-barlist-root,
15
+ .oc-tilemap-root,
16
+ .oc-map-root {
17
+ width: 100%;
18
+ height: 100%;
19
+ }
17
20
 
18
- .oc-tilemap-root {
19
- width: 100%;
20
- }
21
+ .oc-table-root {
22
+ overflow: auto;
23
+ }
21
24
 
22
- .oc-table-root {
23
- overflow: auto;
24
- }
25
+ /* ---------------------------------------------------------------------------
26
+ * Chart container
27
+ * --------------------------------------------------------------------------- */
25
28
 
26
- /* ---------------------------------------------------------------------------
27
- * Chart container
28
- * --------------------------------------------------------------------------- */
29
+ .oc-chart {
30
+ font-family: var(--oc-font-family);
31
+ display: block;
32
+ width: 100%;
33
+ }
29
34
 
30
- .oc-chart {
31
- font-family: var(--oc-font-family);
32
- display: block;
33
- width: 100%;
34
- }
35
+ /* ---------------------------------------------------------------------------
36
+ * BarList SVG container
37
+ * --------------------------------------------------------------------------- */
35
38
 
36
- /* ---------------------------------------------------------------------------
37
- * BarList SVG container
38
- * --------------------------------------------------------------------------- */
39
+ .oc-tilemap,
40
+ .oc-map {
41
+ display: block;
42
+ width: 100%;
43
+ height: auto;
44
+ }
39
45
 
40
- .oc-tilemap {
41
- display: block;
42
- width: 100%;
43
- height: auto;
44
- }
46
+ .oc-map-feature {
47
+ transition: opacity 400ms ease;
48
+ }
45
49
 
46
- .oc-barlist {
47
- display: block;
48
- width: 100%;
49
- }
50
+ .oc-barlist {
51
+ display: block;
52
+ width: 100%;
53
+ }
50
54
 
51
- /* ---------------------------------------------------------------------------
52
- * Screen reader only utility
53
- * --------------------------------------------------------------------------- */
54
-
55
- .oc-sr-only {
56
- position: absolute;
57
- width: 1px;
58
- height: 1px;
59
- padding: 0;
60
- margin: -1px;
61
- overflow: hidden;
62
- clip-path: inset(50%);
63
- white-space: nowrap;
64
- border-width: 0;
65
- }
55
+ /* ---------------------------------------------------------------------------
56
+ * Screen reader only utility
57
+ * --------------------------------------------------------------------------- */
58
+
59
+ .oc-sr-only {
60
+ position: absolute;
61
+ width: 1px;
62
+ height: 1px;
63
+ padding: 0;
64
+ margin: -1px;
65
+ overflow: hidden;
66
+ clip-path: inset(50%);
67
+ white-space: nowrap;
68
+ border-width: 0;
69
+ }
70
+
71
+ /* ---------------------------------------------------------------------------
72
+ * Editable hover feedback
73
+ * --------------------------------------------------------------------------- */
74
+
75
+ .oc-editable-hover {
76
+ outline: 1.5px solid var(--oc-editable-hover);
77
+ outline-offset: 2px;
78
+ border-radius: 2px;
79
+ }
80
+
81
+ /* ---------------------------------------------------------------------------
82
+ * Keyboard focus indicators
83
+ *
84
+ * --oc-focus carries a light value in tokens.css and a dark value in
85
+ * dark.css, so both rings stay visible in either mode.
86
+ * --------------------------------------------------------------------------- */
66
87
 
67
- /* ---------------------------------------------------------------------------
68
- * Editable hover feedback
69
- * --------------------------------------------------------------------------- */
88
+ /* Container ring when the chart itself receives keyboard focus. */
89
+ .oc-root:focus-visible {
90
+ outline: 2px solid var(--oc-focus);
91
+ outline-offset: 2px;
92
+ }
70
93
 
71
- .oc-editable-hover {
72
- outline: 1.5px solid rgba(79, 70, 229, 0.35);
73
- outline-offset: 2px;
74
- border-radius: 2px;
94
+ /* Mark ring during arrow-key navigation (see wireKeyboardNav). Outline on
95
+ * SVG elements draws around the mark's bounding box, same technique as
96
+ * .oc-editable-hover above. */
97
+ .oc-mark-focused {
98
+ outline: 2px solid var(--oc-focus);
99
+ outline-offset: 1px;
100
+ border-radius: 2px;
101
+ }
75
102
  }